diff options
Diffstat (limited to 'libs')
414 files changed, 15876 insertions, 37733 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 0c32fdf5baf5..eeaefc5b157c 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", @@ -168,6 +167,7 @@ cc_test { }, }, data: ["tests/data/**/*.apk"], + test_suites: ["device-tests"], } cc_benchmark { diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 5a8579a275df..237c1e970b17 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -29,6 +29,7 @@ #include "androidfw/Asset.h" #include "androidfw/Idmap.h" +#include "androidfw/misc.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" @@ -39,8 +40,10 @@ using base::unique_fd; static const std::string kResourcesArsc("resources.arsc"); -ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path) - : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) { +ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, + const std::string& path, + time_t last_mod_time) + : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) { } std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) { @@ -116,8 +119,10 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( return {}; } + time_t last_mod_time = getFileModDate(path.c_str()); + // Wrap the handle in a unique_ptr so it gets automatically closed. - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path)); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time)); // Find the resource table. ::ZipEntry entry; @@ -245,4 +250,8 @@ bool ApkAssets::ForEachFile(const std::string& root_path, return result == -1; } +bool ApkAssets::IsUpToDate() const { + return last_mod_time_ == getFileModDate(path_.c_str()); +} + } // namespace android diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp index c512a6b06ed1..9a95fdf80cb5 100644 --- a/libs/androidfw/Asset.cpp +++ b/libs/androidfw/Asset.cpp @@ -292,8 +292,10 @@ Asset::Asset(void) pAsset = new _FileAsset; result = pAsset->openChunk(dataMap); - if (result != NO_ERROR) + if (result != NO_ERROR) { + delete pAsset; return NULL; + } pAsset->mAccessMode = mode; return pAsset; diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index fc625bbaf72d..4755cb866310 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -72,8 +72,11 @@ static volatile int32_t gCount = 0; 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::VENDOR_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::ODM_OVERLAY_DIR = "/odm/overlay"; +const char* AssetManager::OEM_OVERLAY_DIR = "/oem/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"; @@ -348,7 +351,7 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk goto exit; } } - ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc, + ret = tables[1].createIdmap(tables[0], targetCrc, overlayCrc, targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR; } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 04cc5bb30ade..d20aecaaf0f6 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -20,7 +20,9 @@ #include <algorithm> #include <iterator> +#include <map> #include <set> +#include <sstream> #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -33,6 +35,12 @@ #endif #endif +#ifdef __ANDROID__ +#define ANDROID_LOG(x) LOG(x) +#else +#define ANDROID_LOG(x) std::stringstream() +#endif + #include "androidfw/ResourceUtils.h" namespace android { @@ -161,6 +169,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]); + } + } } } @@ -194,6 +209,27 @@ const DynamicRefTable* AssetManager2::GetDynamicRefTableForCookie(ApkAssetsCooki return nullptr; } +const std::unordered_map<std::string, std::string>* + AssetManager2::GetOverlayableMapForPackage(uint32_t package_id) const { + + if (package_id >= package_ids_.size()) { + return nullptr; + } + + const size_t idx = package_ids_[package_id]; + if (idx == 0xff) { + return nullptr; + } + + const PackageGroup& package_group = package_groups_[idx]; + if (package_group.packages_.size() == 0) { + return nullptr; + } + + const auto loaded_package = package_group.packages_[0].loaded_package_; + return &loaded_package->GetOverlayableMap(); +} + void AssetManager2::SetConfiguration(const ResTable_config& configuration) { const int diff = configuration_.diff(configuration); configuration_ = configuration; @@ -209,10 +245,19 @@ std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_ ATRACE_NAME("AssetManager::GetResourceConfigurations"); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { + bool found_system_package = false; for (const ConfiguredPackage& package : package_group.packages_) { if (exclude_system && package.loaded_package_->IsSystem()) { + found_system_package = true; + continue; + } + + if (exclude_system && package.loaded_package_->IsOverlay() && found_system_package) { + // Overlays must appear after the target package to take effect. Any overlay found in the + // same package as a system package is able to overlay system resources. continue; } + package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations); } } @@ -224,10 +269,19 @@ std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, ATRACE_NAME("AssetManager::GetResourceLocales"); std::set<std::string> locales; for (const PackageGroup& package_group : package_groups_) { + bool found_system_package = false; for (const ConfiguredPackage& package : package_group.packages_) { if (exclude_system && package.loaded_package_->IsSystem()) { + found_system_package = true; + continue; + } + + if (exclude_system && package.loaded_package_->IsOverlay() && found_system_package) { + // Overlays must appear after the target package to take effect. Any overlay found in the + // same package as a system package is able to overlay system resources. continue; } + package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales); } } @@ -256,6 +310,9 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con // Start from the back. for (auto iter = apk_assets_.rbegin(); iter != apk_assets_.rend(); ++iter) { const ApkAssets* apk_assets = *iter; + if (apk_assets->IsOverlay()) { + continue; + } auto func = [&](const StringPiece& name, FileType type) { AssetDir::FileInfo info; @@ -282,6 +339,13 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, ApkAssetsCookie* out_cookie) const { for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) { + // Prevent RRO from modifying assets and other entries accessed by file + // path. Explicitly asking for a path in a given package (denoted by a + // cookie) is still OK. + if (apk_assets_[i]->IsOverlay()) { + continue; + } + std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode); if (asset) { if (out_cookie != nullptr) { @@ -308,6 +372,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, bool /*stop_at_first_match*/, + bool ignore_configuration, FindEntryResult* out_entry) const { // Might use this if density_override != 0. ResTable_config density_override_config; @@ -331,7 +396,8 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri const uint8_t package_idx = package_ids_[package_id]; if (package_idx == 0xff) { - LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); + ANDROID_LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + package_id, resid); return kInvalidCookie; } @@ -346,9 +412,12 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri uint32_t best_offset = 0u; uint32_t type_flags = 0u; + Resolution::Step::Type resolution_type; + std::vector<Resolution::Step> resolution_steps; + // If desired_config is the same as the set configuration, then we can use our filtered list // and we don't need to match the configurations, since they already matched. - const bool use_fast_path = desired_config == &configuration_; + const bool use_fast_path = !ignore_configuration && desired_config == &configuration_; for (size_t pi = 0; pi < package_count; pi++) { const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; @@ -377,8 +446,8 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri // If the package is an overlay, then even configurations that are the same MUST be chosen. const bool package_is_overlay = loaded_package->IsOverlay(); - const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; if (use_fast_path) { + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations; const size_t type_count = candidate_configs.size(); for (uint32_t i = 0; i < type_count; i++) { @@ -386,21 +455,34 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri // We can skip calling ResTable_config::match() because we know that all candidate // configurations that do NOT match have been filtered-out. - if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || - (package_is_overlay && this_config.compare(*best_config) == 0)) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const ResTable_type* type_chunk = filtered_group.types[i]; - const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } + if (best_config == nullptr) { + resolution_type = Resolution::Step::Type::INITIAL; + } else if (this_config.isBetterThan(*best_config, desired_config)) { + resolution_type = Resolution::Step::Type::BETTER_MATCH; + } else if (package_is_overlay && this_config.compare(*best_config) == 0) { + resolution_type = Resolution::Step::Type::OVERLAID; + } else { + continue; + } + + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const ResTable_type* type = filtered_group.types[i]; + const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = type; + best_config = &this_config; + best_offset = offset; - best_cookie = cookie; - best_package = loaded_package; - best_type = type_chunk; - best_config = &this_config; - best_offset = offset; + if (resource_resolution_logging_enabled_) { + resolution_steps.push_back(Resolution::Step{resolution_type, + this_config.toString(), + &loaded_package->GetPackageName()}); } } } else { @@ -411,26 +493,48 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri // ResTable_config, we must copy it. const auto iter_end = type_spec->types + type_spec->type_count; for (auto iter = type_spec->types; iter != iter_end; ++iter) { - ResTable_config this_config; - this_config.copyFromDtoH((*iter)->config); - - if (this_config.match(*desired_config)) { - if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || - (package_is_overlay && this_config.compare(*best_config) == 0)) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } + ResTable_config this_config{}; - best_cookie = cookie; - best_package = loaded_package; - best_type = *iter; - best_config_copy = this_config; - best_config = &best_config_copy; - best_offset = offset; + if (!ignore_configuration) { + this_config.copyFromDtoH((*iter)->config); + if (!this_config.match(*desired_config)) { + continue; } + + if (best_config == nullptr) { + resolution_type = Resolution::Step::Type::INITIAL; + } else if (this_config.isBetterThan(*best_config, desired_config)) { + resolution_type = Resolution::Step::Type::BETTER_MATCH; + } else if (package_is_overlay && this_config.compare(*best_config) == 0) { + resolution_type = Resolution::Step::Type::OVERLAID; + } else { + continue; + } + } + + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = *iter; + best_config_copy = this_config; + best_config = &best_config_copy; + best_offset = offset; + + if (ignore_configuration) { + // Any configuration will suffice, so break. + break; + } + + if (resource_resolution_logging_enabled_) { + resolution_steps.push_back(Resolution::Step{resolution_type, + this_config.toString(), + &loaded_package->GetPackageName()}); } } } @@ -452,55 +556,136 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri out_entry->entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; + + if (resource_resolution_logging_enabled_) { + last_resolution.resid = resid; + last_resolution.cookie = best_cookie; + last_resolution.steps = resolution_steps; + + // Cache only the type/entry refs since that's all that's needed to build name + last_resolution.type_string_ref = + StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1); + last_resolution.entry_string_ref = + StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); + } + return best_cookie; } -bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { - FindEntryResult entry; - ApkAssetsCookie cookie = - FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry); +void AssetManager2::SetResourceResolutionLoggingEnabled(bool enabled) { + resource_resolution_logging_enabled_ = enabled; + + if (!enabled) { + last_resolution.cookie = kInvalidCookie; + last_resolution.resid = 0; + last_resolution.steps.clear(); + last_resolution.type_string_ref = StringPoolRef(); + last_resolution.entry_string_ref = StringPoolRef(); + } +} + +std::string AssetManager2::GetLastResourceResolution() const { + if (!resource_resolution_logging_enabled_) { + LOG(ERROR) << "Must enable resource resolution logging before getting path."; + return std::string(); + } + + auto cookie = last_resolution.cookie; if (cookie == kInvalidCookie) { - return false; + LOG(ERROR) << "AssetManager hasn't resolved a resource to read resolution path."; + return std::string(); } + uint32_t resid = last_resolution.resid; + std::vector<Resolution::Step>& steps = last_resolution.steps; + + ResourceName resource_name; + std::string resource_name_string; + const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); - if (package == nullptr) { - return false; + + if (package != nullptr) { + ToResourceName(last_resolution.type_string_ref, + last_resolution.entry_string_ref, + package->GetPackageName(), + &resource_name); + resource_name_string = ToFormattedResourceString(&resource_name); } - out_name->package = package->GetPackageName().data(); - out_name->package_len = package->GetPackageName().size(); + std::stringstream log_stream; + log_stream << base::StringPrintf("Resolution for 0x%08x ", resid) + << resource_name_string + << "\n\tFor config -" + << configuration_.toString(); + + std::string prefix; + for (Resolution::Step step : steps) { + switch (step.type) { + case Resolution::Step::Type::INITIAL: + prefix = "Found initial"; + break; + case Resolution::Step::Type::BETTER_MATCH: + prefix = "Found better"; + break; + case Resolution::Step::Type::OVERLAID: + prefix = "Overlaid"; + break; + } - out_name->type = entry.type_string_ref.string8(&out_name->type_len); - out_name->type16 = nullptr; - if (out_name->type == nullptr) { - out_name->type16 = entry.type_string_ref.string16(&out_name->type_len); - if (out_name->type16 == nullptr) { - return false; + if (!prefix.empty()) { + log_stream << "\n\t" << prefix << ": " << *step.package_name; + + if (!step.config_name.isEmpty()) { + log_stream << " -" << step.config_name; + } } } - out_name->entry = entry.entry_string_ref.string8(&out_name->entry_len); - out_name->entry16 = nullptr; - if (out_name->entry == nullptr) { - out_name->entry16 = entry.entry_string_ref.string16(&out_name->entry_len); - if (out_name->entry16 == nullptr) { - return false; - } + return log_stream.str(); +} + +bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { + FindEntryResult entry; + ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, + true /* stop_at_first_match */, + true /* ignore_configuration */, &entry); + if (cookie == kInvalidCookie) { + return false; } - return true; + + const uint8_t package_idx = package_ids_[get_package_id(resid)]; + if (package_idx == 0xff) { + LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", + get_package_id(resid), resid); + return false; + } + + const PackageGroup& package_group = package_groups_[package_idx]; + auto cookie_iter = std::find(package_group.cookies_.begin(), + package_group.cookies_.end(), cookie); + if (cookie_iter == package_group.cookies_.end()) { + return false; + } + + long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter); + const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_; + return ToResourceName(entry.type_string_ref, + entry.entry_string_ref, + package->GetPackageName(), + out_name); } bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const { FindEntryResult entry; - ApkAssetsCookie cookie = - FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); + ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, + false /* stop_at_first_match */, + true /* ignore_configuration */, &entry); if (cookie != kInvalidCookie) { *out_flags = entry.type_flags; - return cookie; + return true; } - return kInvalidCookie; + return false; } ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, @@ -508,8 +693,8 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, ResTable_config* out_selected_config, uint32_t* out_flags) const { FindEntryResult entry; - ApkAssetsCookie cookie = - FindEntry(resid, density_override, false /* stop_at_first_match */, &entry); + ApkAssetsCookie cookie = FindEntry(resid, density_override, false /* stop_at_first_match */, + false /* ignore_configuration */, &entry); if (cookie == kInvalidCookie) { return kInvalidCookie; } @@ -567,22 +752,42 @@ ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_valu return cookie; } +const std::vector<uint32_t> AssetManager2::GetBagResIdStack(uint32_t resid) { + auto cached_iter = cached_bag_resid_stacks_.find(resid); + if (cached_iter != cached_bag_resid_stacks_.end()) { + return cached_iter->second; + } else { + auto found_resids = std::vector<uint32_t>(); + GetBag(resid, found_resids); + // Cache style stacks if they are not already cached. + cached_bag_resid_stacks_[resid] = found_resids; + return found_resids; + } +} + const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { auto found_resids = std::vector<uint32_t>(); - return GetBag(resid, found_resids); + auto bag = GetBag(resid, found_resids); + + // Cache style stacks if they are not already cached. + auto cached_iter = cached_bag_resid_stacks_.find(resid); + if (cached_iter == cached_bag_resid_stacks_.end()) { + cached_bag_resid_stacks_[resid] = found_resids; + } + return bag; } const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) { - ATRACE_NAME("AssetManager::GetBag"); - auto cached_iter = cached_bags_.find(resid); if (cached_iter != cached_bags_.end()) { return cached_iter->second.get(); } FindEntryResult entry; - ApkAssetsCookie cookie = - FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); + ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, + false /* stop_at_first_match */, + false /* ignore_configuration */, + &entry); if (cookie == kInvalidCookie) { return nullptr; } @@ -629,6 +834,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->key = new_key; new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; + new_entry->style = resid; new_entry->value.copyFrom_dtoh(map_entry->value); status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); if (err != NO_ERROR) { @@ -687,6 +893,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; new_entry->value.copyFrom_dtoh(map_entry->value); + new_entry->style = resid; status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); if (err != NO_ERROR) { LOG(ERROR) << base::StringPrintf( @@ -723,6 +930,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->key_pool = nullptr; new_entry->type_pool = nullptr; new_entry->value.copyFrom_dtoh(map_entry->value); + new_entry->style = resid; status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value); if (err != NO_ERROR) { LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", @@ -852,6 +1060,8 @@ void AssetManager2::RebuildFilterList(bool filter_incompatible_configs) { } void AssetManager2::InvalidateCaches(uint32_t diff) { + cached_bag_resid_stacks_.clear(); + if (diff == 0xffffffffu) { // Everything must go. cached_bags_.clear(); @@ -869,6 +1079,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 +1275,242 @@ 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; + } + + src_to_dest_asset_cookies.insert(std::make_pair(i, j)); + src_asset_cookie_id_map.insert(std::make_pair(i, package_map)); + break; + } + } } - if (packages_[p] == nullptr) { - // The other theme has this package, but we don't. Make one. - packages_[p].reset(new Package()); + // 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; + } + + bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_REFERENCE + || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE) + && entry.value.data != 0x0; + + // 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 attribute_data = entry.value.data; + if (is_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; + } + + attribute_data = fix_package_id(entry.value.data, value_dest_package->second); + } + + // Find the cookie of the value in the destination. If the source apk is not loaded in the + // destination, only copy resources that do not reference resources in the source. + ApkAssetsCookie data_dest_cookie; + auto value_dest_cookie = src_to_dest_asset_cookies.find(entry.cookie); + if (value_dest_cookie != src_to_dest_asset_cookies.end()) { + data_dest_cookie = value_dest_cookie->second; + } else { + if (is_reference || entry.value.dataType == Res_value::TYPE_STRING) { + continue; + } else { + data_dest_cookie = 0x0; + } + } + + // The package id of the attribute needs to be rewritten to the package id of the + // attribute in the destination. + int attribute_dest_package_id = p; + if (attribute_dest_package_id != 0x01) { + // Find the cookie of the attribute resource id in the source AssetManager + FindEntryResult attribute_entry_result; + ApkAssetsCookie attribute_cookie = + o.asset_manager_->FindEntry(make_resid(p, t, e), 0 /* density_override */ , + true /* stop_at_first_match */, + true /* ignore_configuration */, + &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; + } + + // 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; + } + + dest_type->entries[e].cookie = data_dest_cookie; + dest_type->entries[e].value.dataType = entry.value.dataType; + dest_type->entries[e].value.data = attribute_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/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 2fcecf252a26..e62fb614e195 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -286,6 +286,7 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; config.density = 0; + uint32_t value_source_resid = 0; // Try to find a value for this attribute... we prioritize values // coming from, first XML attributes, then XML style, then default @@ -299,6 +300,7 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, if (kDebugStyles) { ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data); } + value_source_resid = xml_parser->getSourceResourceId(); } if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { @@ -309,8 +311,10 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, cookie = entry->cookie; type_set_flags = style_flags; value = entry->value; + value_source_resid = entry->style; if (kDebugStyles) { - ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data); + ALOGI("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data, + entry->style); } } } @@ -324,8 +328,10 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, type_set_flags = def_style_flags; value = entry->value; if (kDebugStyles) { - ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); + ALOGI("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data, + entry->style); } + value_source_resid = entry->style; } } @@ -344,6 +350,7 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + // TODO: set value_source_resid for the style in the theme that was used. if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); @@ -381,6 +388,7 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; + out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid; if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) { indices_idx++; @@ -388,7 +396,6 @@ void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, // out_indices must NOT be nullptr. out_indices[indices_idx] = ii; } - out_values += STYLE_NUM_ENTRIES; } diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index a99e77f8dbb9..e1067fcd4d3d 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -49,15 +49,21 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o int ashmemFd = ashmem_create_region(ashmemName.string(), size); if (ashmemFd < 0) { result = -errno; + ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno); } else { result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE); - if (result >= 0) { + if (result < 0) { + ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno); + } else { void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); if (data == MAP_FAILED) { result = -errno; + ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); } else { result = ashmem_set_prot_region(ashmemFd, PROT_READ); - if (result >= 0) { + if (result < 0) { + ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno); + } else { CursorWindow* window = new CursorWindow(name, ashmemFd, data, size, false /*readOnly*/); result = window->clear(); @@ -86,26 +92,34 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor String8 name = parcel->readString8(); status_t result; + int actualSize; int ashmemFd = parcel->readFileDescriptor(); if (ashmemFd == int(BAD_TYPE)) { result = BAD_TYPE; + ALOGE("CursorWindow: readFileDescriptor() failed"); } else { ssize_t size = ashmem_get_size_region(ashmemFd); if (size < 0) { result = UNKNOWN_ERROR; + ALOGE("CursorWindow: ashmem_get_size_region() failed: errno=%d.", errno); } else { int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0); if (dupAshmemFd < 0) { result = -errno; + ALOGE("CursorWindow: fcntl() failed: errno=%d.", errno); } else { // the size of the ashmem descriptor can be modified between ashmem_get_size_region // call and mmap, so we'll check again immediately after memory is mapped void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0); if (data == MAP_FAILED) { result = -errno; - } else if (ashmem_get_size_region(dupAshmemFd) != size) { + ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); + } else if ((actualSize = ashmem_get_size_region(dupAshmemFd)) != size) { ::munmap(data, size); result = BAD_VALUE; + ALOGE("CursorWindow: ashmem_get_size_region() returned %d, expected %d" + " errno=%d", + actualSize, (int) size, errno); } else { CursorWindow* window = new CursorWindow(name, dupAshmemFd, data, size, true /*readOnly*/); diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp index 7708e4340397..660614895603 100644 --- a/libs/androidfw/DisplayEventDispatcher.cpp +++ b/libs/androidfw/DisplayEventDispatcher.cpp @@ -68,7 +68,7 @@ status_t DisplayEventDispatcher::scheduleVsync() { // Drain all pending events. nsecs_t vsyncTimestamp; - int32_t vsyncDisplayId; + PhysicalDisplayId vsyncDisplayId; uint32_t vsyncCount; if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", @@ -101,10 +101,11 @@ int DisplayEventDispatcher::handleEvent(int, int events, void*) { // Drain all pending events, keep the last vsync. nsecs_t vsyncTimestamp; - int32_t vsyncDisplayId; + PhysicalDisplayId vsyncDisplayId; uint32_t vsyncCount; if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { - ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d", + ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", displayId=%" + ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d", this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount); mWaitingForVsync = false; dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount); @@ -114,7 +115,7 @@ int DisplayEventDispatcher::handleEvent(int, int events, void*) { } bool DisplayEventDispatcher::processPendingEvents( - nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) { + nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount) { bool gotVsync = false; DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; ssize_t n; @@ -128,11 +129,14 @@ bool DisplayEventDispatcher::processPendingEvents( // ones. That's fine, we only care about the most recent. gotVsync = true; *outTimestamp = ev.header.timestamp; - *outId = ev.header.id; + *outDisplayId = ev.header.displayId; *outCount = ev.vsync.count; break; case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: - dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected); + dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected); + break; + case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED: + dispatchConfigChanged(ev.header.timestamp, ev.header.displayId, ev.config.configId); break; default: ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type); diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 68d216d286cf..72873abc6a42 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -583,7 +583,80 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, loaded_package->dynamic_package_map_.emplace_back(std::move(package_name), dtohl(entry_iter->packageId)); } + } break; + + case RES_TABLE_OVERLAYABLE_TYPE: { + const ResTable_overlayable_header* header = + child_chunk.header<ResTable_overlayable_header>(); + if (header == nullptr) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_TYPE too small."; + return {}; + } + std::string name; + util::ReadUtf16StringFromDevice(header->name, arraysize(header->name), &name); + std::string actor; + util::ReadUtf16StringFromDevice(header->actor, arraysize(header->actor), &actor); + + if (loaded_package->overlayable_map_.find(name) != + loaded_package->overlayable_map_.end()) { + LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'."; + return {}; + } + loaded_package->overlayable_map_.emplace(name, actor); + + // Iterate over the overlayable policy chunks contained within the overlayable chunk data + ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size()); + while (overlayable_iter.HasNext()) { + const Chunk overlayable_child_chunk = overlayable_iter.Next(); + + switch (overlayable_child_chunk.type()) { + case RES_TABLE_OVERLAYABLE_POLICY_TYPE: { + const ResTable_overlayable_policy_header* policy_header = + overlayable_child_chunk.header<ResTable_overlayable_policy_header>(); + if (policy_header == nullptr) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small."; + return {}; + } + + if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref)) + < dtohl(policy_header->entry_count)) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries."; + return {}; + } + + // Retrieve all the resource ids belonging to this policy chunk + std::unordered_set<uint32_t> ids; + const auto ids_begin = + reinterpret_cast<const ResTable_ref*>(overlayable_child_chunk.data_ptr()); + const auto ids_end = ids_begin + dtohl(policy_header->entry_count); + for (auto id_iter = ids_begin; id_iter != ids_end; ++id_iter) { + ids.insert(dtohl(id_iter->ident)); + } + + // Add the pairing of overlayable properties and resource ids to the package + OverlayableInfo overlayable_info{}; + overlayable_info.name = name; + overlayable_info.actor = actor; + overlayable_info.policy_flags = policy_header->policy_flags; + loaded_package->overlayable_infos_.push_back(std::make_pair(overlayable_info, ids)); + loaded_package->defines_overlayable_ = true; + break; + } + + default: + LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); + break; + } + } + + if (overlayable_iter.HadError()) { + LOG(ERROR) << StringPrintf("Error parsing RES_TABLE_OVERLAYABLE_TYPE: %s", + overlayable_iter.GetLastError().c_str()); + if (overlayable_iter.HadFatalError()) { + return {}; + } + } } break; default: 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..2ad2e76cc696 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -26,9 +26,12 @@ #include <algorithm> #include <limits> +#include <map> #include <memory> +#include <set> #include <type_traits> +#include <android-base/macros.h> #include <androidfw/ByteBucketArray.h> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> @@ -1591,6 +1594,16 @@ void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos) mCurExt = pos.curExt; } +void ResXMLParser::setSourceResourceId(const uint32_t resId) +{ + mSourceResourceId = resId; +} + +uint32_t ResXMLParser::getSourceResourceId() const +{ + return mSourceResourceId; +} + // -------------------------------------------------------------------- static volatile int32_t gCount = 0; @@ -3073,6 +3086,7 @@ struct LocaleParserState { } break; } + FALLTHROUGH_INTENDED; case 5: case 6: case 7: @@ -3205,20 +3219,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 +3233,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) { @@ -3496,6 +3510,7 @@ struct ResTable::Package ResStringPool keyStrings; size_t typeIdOffset; + bool definesOverlayable = false; }; // A group of objects describing a particular resource package. @@ -6847,6 +6862,10 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, ALOGW("Found multiple library tables, ignoring..."); } } else { + if (ctype == RES_TABLE_OVERLAYABLE_TYPE) { + package->definesOverlayable = true; + } + status_t err = validate_chunk(chunk, sizeof(ResChunk_header), endPos, "ResTable_package:unknown"); if (err != NO_ERROR) { @@ -6960,6 +6979,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 +7019,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); @@ -7024,178 +7050,211 @@ status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { return NO_ERROR; } -struct IdmapTypeMap { - ssize_t overlayTypeId; - size_t entryOffset; - Vector<uint32_t> entryMap; +class IdmapMatchingResources; + +class IdmapTypeMapping { +public: + void add(uint32_t targetResId, uint32_t overlayResId) { + uint8_t targetTypeId = Res_GETTYPE(targetResId); + if (mData.find(targetTypeId) == mData.end()) { + mData.emplace(targetTypeId, std::set<std::pair<uint32_t, uint32_t>>()); + } + auto& entries = mData[targetTypeId]; + entries.insert(std::make_pair(targetResId, overlayResId)); + } + + bool empty() const { + return mData.empty(); + } + +private: + // resource type ID in context of target -> set of resource entries mapping target -> overlay + std::map<uint8_t, std::set<std::pair<uint32_t, uint32_t>>> mData; + + friend IdmapMatchingResources; +}; + +class IdmapMatchingResources { +public: + IdmapMatchingResources(std::unique_ptr<IdmapTypeMapping> tm) : mTypeMapping(std::move(tm)) { + assert(mTypeMapping); + for (auto ti = mTypeMapping->mData.cbegin(); ti != mTypeMapping->mData.cend(); ++ti) { + uint32_t lastSeen = 0xffffffff; + size_t totalEntries = 0; + for (auto ei = ti->second.cbegin(); ei != ti->second.cend(); ++ei) { + assert(lastSeen == 0xffffffff || lastSeen < ei->first); + mEntryPadding[ei->first] = (lastSeen == 0xffffffff) ? 0 : ei->first - lastSeen - 1; + lastSeen = ei->first; + totalEntries += 1 + mEntryPadding[ei->first]; + } + mNumberOfEntriesIncludingPadding[ti->first] = totalEntries; + } + } + + const std::map<uint8_t, std::set<std::pair<uint32_t, uint32_t>>>& getTypeMapping() const { + return mTypeMapping->mData; + } + + size_t getNumberOfEntriesIncludingPadding(uint8_t type) const { + return mNumberOfEntriesIncludingPadding.at(type); + } + + size_t getPadding(uint32_t resid) const { + return mEntryPadding.at(resid); + } + +private: + // resource type ID in context of target -> set of resource entries mapping target -> overlay + const std::unique_ptr<IdmapTypeMapping> mTypeMapping; + + // resource ID in context of target -> trailing padding for that resource (call FixPadding + // before use) + std::map<uint32_t, size_t> mEntryPadding; + + // resource type ID in context of target -> total number of entries, including padding entries, + // for that type (call FixPadding before use) + std::map<uint8_t, size_t> mNumberOfEntriesIncludingPadding; }; -status_t ResTable::createIdmap(const ResTable& overlay, +status_t ResTable::createIdmap(const ResTable& targetResTable, uint32_t targetCrc, uint32_t overlayCrc, const char* targetPath, const char* overlayPath, void** outData, size_t* outSize) const { - // see README for details on the format of map - if (mPackageGroups.size() == 0) { - ALOGW("idmap: target package has no package groups, cannot create idmap\n"); + if (targetPath == NULL || overlayPath == NULL || outData == NULL || outSize == NULL) { + ALOGE("idmap: unexpected NULL parameter"); return UNKNOWN_ERROR; } - - if (mPackageGroups[0]->packages.size() == 0) { - ALOGW("idmap: target package has no packages in its first package group, " - "cannot create idmap\n"); + if (strlen(targetPath) > 255) { + ALOGE("idmap: target path exceeds idmap file format limit of 255 chars"); + return UNKNOWN_ERROR; + } + if (strlen(overlayPath) > 255) { + ALOGE("idmap: overlay path exceeds idmap file format limit of 255 chars"); + return UNKNOWN_ERROR; + } + if (mPackageGroups.size() == 0 || mPackageGroups[0]->packages.size() == 0) { + ALOGE("idmap: invalid overlay package"); + return UNKNOWN_ERROR; + } + if (targetResTable.mPackageGroups.size() == 0 || + targetResTable.mPackageGroups[0]->packages.size() == 0) { + ALOGE("idmap: invalid target package"); return UNKNOWN_ERROR; } - // The number of resources overlaid that were not explicitly marked overlayable. - size_t forcedOverlayCount = 0u; - - KeyedVector<uint8_t, IdmapTypeMap> map; - - // overlaid packages are assumed to contain only one package group - const PackageGroup* pg = mPackageGroups[0]; - - // starting size is header - *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES; + // Idmap is not aware of overlayable, exit since policy checks can't be done + if (targetResTable.mPackageGroups[0]->packages[0]->definesOverlayable) { + return UNKNOWN_ERROR; + } - // target package id and number of types in map - *outSize += 2 * sizeof(uint16_t); + const ResTable_package* targetPackageStruct = + targetResTable.mPackageGroups[0]->packages[0]->package; + const size_t tmpNameSize = arraysize(targetPackageStruct->name); + char16_t tmpName[tmpNameSize]; + strcpy16_dtoh(tmpName, targetPackageStruct->name, tmpNameSize); + const String16 targetPackageName(tmpName); - // overlay packages are assumed to contain only one package group - const ResTable_package* overlayPackageStruct = overlay.mPackageGroups[0]->packages[0]->package; - char16_t tmpName[sizeof(overlayPackageStruct->name)/sizeof(overlayPackageStruct->name[0])]; - strcpy16_dtoh(tmpName, overlayPackageStruct->name, sizeof(overlayPackageStruct->name)/sizeof(overlayPackageStruct->name[0])); - const String16 overlayPackage(tmpName); + const PackageGroup* packageGroup = mPackageGroups[0]; - for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) { - const TypeList& typeList = pg->types[typeIndex]; + // find the resources that exist in both packages + auto typeMapping = std::make_unique<IdmapTypeMapping>(); + for (size_t typeIndex = 0; typeIndex < packageGroup->types.size(); ++typeIndex) { + const TypeList& typeList = packageGroup->types[typeIndex]; if (typeList.isEmpty()) { continue; } - const Type* typeConfigs = typeList[0]; - IdmapTypeMap typeMap; - typeMap.overlayTypeId = -1; - typeMap.entryOffset = 0; - for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) { - uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex); - resource_name resName; - if (!this->getResourceName(resID, false, &resName)) { - if (typeMap.entryMap.isEmpty()) { - typeMap.entryOffset++; - } + uint32_t overlay_resid = Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex); + resource_name current_res; + if (!getResourceName(overlay_resid, false, ¤t_res)) { continue; } uint32_t typeSpecFlags = 0u; - const String16 overlayType(resName.type, resName.typeLen); - const String16 overlayName(resName.name, resName.nameLen); - uint32_t overlayResID = overlay.identifierForName(overlayName.string(), - overlayName.size(), - overlayType.string(), - overlayType.size(), - overlayPackage.string(), - overlayPackage.size(), - &typeSpecFlags); - if (overlayResID == 0) { - // No such target resource was found. - if (typeMap.entryMap.isEmpty()) { - typeMap.entryOffset++; - } + const uint32_t target_resid = targetResTable.identifierForName( + current_res.name, + current_res.nameLen, + current_res.type, + current_res.typeLen, + targetPackageName.string(), + targetPackageName.size(), + &typeSpecFlags); + + if (target_resid == 0) { continue; } - // Now that we know this is being overlaid, check if it can be, and emit a warning if - // it can't. - if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) & - ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) { - forcedOverlayCount++; - } - - if (typeMap.overlayTypeId == -1) { - typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1; - } - - if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) { - ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x" - " but entries should map to resources of type %02zx", - resID, overlayResID, typeMap.overlayTypeId); - return BAD_TYPE; - } - - if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) { - // pad with 0xffffffff's (indicating non-existing entries) before adding this entry - size_t index = typeMap.entryMap.size(); - size_t numItems = entryIndex - (typeMap.entryOffset + index); - if (typeMap.entryMap.insertAt(0xffffffff, index, numItems) < 0) { - return NO_MEMORY; - } - } - typeMap.entryMap.add(Res_GETENTRY(overlayResID)); - } - - if (!typeMap.entryMap.isEmpty()) { - if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) { - return NO_MEMORY; - } - *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t)); + typeMapping->add(target_resid, overlay_resid); } } - if (map.isEmpty()) { - ALOGW("idmap: no resources in overlay package present in base package"); + if (typeMapping->empty()) { + ALOGE("idmap: no matching resources"); return UNKNOWN_ERROR; } + const IdmapMatchingResources matchingResources(std::move(typeMapping)); + + // write idmap + *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES; // magic, version, target and overlay crc + *outSize += 2 * sizeof(uint16_t); // target package id, type count + auto fixedTypeMapping = matchingResources.getTypeMapping(); + const auto typesEnd = fixedTypeMapping.cend(); + for (auto ti = fixedTypeMapping.cbegin(); ti != typesEnd; ++ti) { + *outSize += 4 * sizeof(uint16_t); // target type, overlay type, entry count, entry offset + *outSize += matchingResources.getNumberOfEntriesIncludingPadding(ti->first) * + sizeof(uint32_t); // entries + } if ((*outData = malloc(*outSize)) == NULL) { return NO_MEMORY; } - uint32_t* data = (uint32_t*)*outData; - *data++ = htodl(IDMAP_MAGIC); - *data++ = htodl(ResTable::IDMAP_CURRENT_VERSION); - *data++ = htodl(targetCrc); - *data++ = htodl(overlayCrc); - const char* paths[] = { targetPath, overlayPath }; - for (int j = 0; j < 2; ++j) { - char* p = (char*)data; - const char* path = paths[j]; - const size_t I = strlen(path); - if (I > 255) { - ALOGV("path exceeds expected 255 characters: %s\n", path); - return UNKNOWN_ERROR; - } - for (size_t i = 0; i < 256; ++i) { - *p++ = i < I ? path[i] : '\0'; - } - data += 256 / sizeof(uint32_t); + // write idmap header + uint32_t* data = reinterpret_cast<uint32_t*>(*outData); + *data++ = htodl(IDMAP_MAGIC); // write: magic + *data++ = htodl(ResTable::IDMAP_CURRENT_VERSION); // write: version + *data++ = htodl(targetCrc); // write: target crc + *data++ = htodl(overlayCrc); // write: overlay crc + + char* charData = reinterpret_cast<char*>(data); + size_t pathLen = strlen(targetPath); + for (size_t i = 0; i < 256; ++i) { + *charData++ = i < pathLen ? targetPath[i] : '\0'; // write: target path + } + pathLen = strlen(overlayPath); + for (size_t i = 0; i < 256; ++i) { + *charData++ = i < pathLen ? overlayPath[i] : '\0'; // write: overlay path } - const size_t mapSize = map.size(); + data += (2 * 256) / sizeof(uint32_t); + + // write idmap data header uint16_t* typeData = reinterpret_cast<uint16_t*>(data); - *typeData++ = htods(pg->id); - *typeData++ = htods(mapSize); - for (size_t i = 0; i < mapSize; ++i) { - uint8_t targetTypeId = map.keyAt(i); - const IdmapTypeMap& typeMap = map[i]; - *typeData++ = htods(targetTypeId + 1); - *typeData++ = htods(typeMap.overlayTypeId); - *typeData++ = htods(typeMap.entryMap.size()); - *typeData++ = htods(typeMap.entryOffset); - - const size_t entryCount = typeMap.entryMap.size(); - uint32_t* entries = reinterpret_cast<uint32_t*>(typeData); - for (size_t j = 0; j < entryCount; j++) { - entries[j] = htodl(typeMap.entryMap[j]); + *typeData++ = htods(targetPackageStruct->id); // write: target package id + *typeData++ = + htods(static_cast<uint16_t>(fixedTypeMapping.size())); // write: type count + + // write idmap data + for (auto ti = fixedTypeMapping.cbegin(); ti != typesEnd; ++ti) { + const size_t entryCount = matchingResources.getNumberOfEntriesIncludingPadding(ti->first); + auto ei = ti->second.cbegin(); + *typeData++ = htods(Res_GETTYPE(ei->first) + 1); // write: target type id + *typeData++ = htods(Res_GETTYPE(ei->second) + 1); // write: overlay type id + *typeData++ = htods(entryCount); // write: entry count + *typeData++ = htods(Res_GETENTRY(ei->first)); // write: (target) entry offset + uint32_t *entryData = reinterpret_cast<uint32_t*>(typeData); + for (; ei != ti->second.cend(); ++ei) { + const size_t padding = matchingResources.getPadding(ei->first); + for (size_t i = 0; i < padding; ++i) { + *entryData++ = htodl(0xffffffff); // write: padding + } + *entryData++ = htodl(Res_GETENTRY(ei->second)); // write: (overlay) entry } typeData += entryCount * 2; } - if (forcedOverlayCount > 0) { - ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount); - } - return NO_ERROR; } diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index d63feb01ef83..c63dff8f9104 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -48,4 +48,65 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin !(has_type_separator && out_type->empty()); } +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, + AssetManager2::ResourceName* out_name) { + out_name->package = package_name.data(); + out_name->package_len = package_name.size(); + + out_name->type = type_string_ref.string8(&out_name->type_len); + out_name->type16 = nullptr; + if (out_name->type == nullptr) { + out_name->type16 = type_string_ref.string16(&out_name->type_len); + if (out_name->type16 == nullptr) { + return false; + } + } + + out_name->entry = entry_string_ref.string8(&out_name->entry_len); + out_name->entry16 = nullptr; + if (out_name->entry == nullptr) { + out_name->entry16 = entry_string_ref.string16(&out_name->entry_len); + if (out_name->entry16 == nullptr) { + return false; + } + } + + return true; +} + +std::string ToFormattedResourceString(AssetManager2::ResourceName* resource_name) { + std::string result; + if (resource_name->package != nullptr) { + result.append(resource_name->package, resource_name->package_len); + } + + if (resource_name->type != nullptr || resource_name->type16 != nullptr) { + if (!result.empty()) { + result += ":"; + } + + if (resource_name->type != nullptr) { + result.append(resource_name->type, resource_name->type_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(resource_name->type16, resource_name->type_len)); + } + } + + if (resource_name->entry != nullptr || resource_name->entry16 != nullptr) { + if (!result.empty()) { + result += "/"; + } + + if (resource_name->entry != nullptr) { + result.append(resource_name->entry, resource_name->entry_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(resource_name->entry16, resource_name->entry_len)); + } + } + + return result; +} + } // namespace android diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING new file mode 100644 index 000000000000..a58b47fcff9d --- /dev/null +++ b/libs/androidfw/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "presubmit": [ + { + "name": "libandroidfw_tests", + "host": true + } + ] +}
\ No newline at end of file diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index db2d0382bcf6..49fc82bff11e 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -80,6 +80,12 @@ class ApkAssets { return loaded_arsc_.get(); } + inline bool IsOverlay() const { + return idmap_asset_.get() != nullptr; + } + + bool IsUpToDate() const; + private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); @@ -91,12 +97,13 @@ class ApkAssets { // Creates an Asset from any file on the file system. static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); - ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path); + ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time); using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>; ZipArchivePtr zip_handle_; const std::string path_; + time_t last_mod_time_; std::unique_ptr<Asset> resources_asset_; std::unique_ptr<Asset> idmap_asset_; std::unique_ptr<const LoadedArsc> loaded_arsc_; diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index 08da7319de85..66fba26b7289 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -59,12 +59,15 @@ class AssetManager : public AAssetManager { public: static const char* RESOURCES_FILENAME; static const char* IDMAP_BIN; - static const char* OVERLAY_DIR; + static const char* VENDOR_OVERLAY_DIR; static const char* PRODUCT_OVERLAY_DIR; + static const char* PRODUCT_SERVICES_OVERLAY_DIR; + static const char* ODM_OVERLAY_DIR; + static const char* OEM_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 - * OVERLAY_DIR. + * APKs in VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in + * addition to VENDOR_OVERLAY_DIR. */ static const char* OVERLAY_THEME_DIR_PROPERTY; static const char* TARGET_PACKAGE_NAME; diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 2f0ee01639fe..1e2b36cb1703 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -49,6 +49,9 @@ struct ResolvedBag { Res_value value; + // The resource ID of the origin style associated with the given entry. + uint32_t style; + // Which ApkAssets this entry came from. ApkAssetsCookie cookie; @@ -74,6 +77,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; @@ -119,6 +124,9 @@ class AssetManager2 { // This may be nullptr if the APK represented by `cookie` has no resource table. const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const; + const std::unordered_map<std::string, std::string>* + GetOverlayableMapForPackage(uint32_t package_id) const; + // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. void SetConfiguration(const ResTable_config& configuration); @@ -224,6 +232,16 @@ class AssetManager2 { ResTable_config* in_out_selected_config, uint32_t* in_out_flags, uint32_t* out_last_reference) const; + // Enables or disables resource resolution logging. Clears stored steps when + // disabled. + void SetResourceResolutionLoggingEnabled(bool enabled); + + // Returns formatted log of last resource resolution path, or empty if no + // resource has been resolved yet. + std::string GetLastResourceResolution() const; + + const std::vector<uint32_t> GetBagResIdStack(uint32_t resid); + // Retrieves the best matching bag/map resource with ID `resid`. // This method will resolve all parent references for this bag and merge keys with the child. // To iterate over the keys, use the following idiom: @@ -239,11 +257,12 @@ class AssetManager2 { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); - template <typename Func> - void ForEachPackage(Func func) const { + void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func) const { for (const PackageGroup& package_group : package_groups_) { - func(package_group.packages_.front().loaded_package_->GetPackageName(), - package_group.dynamic_ref_table.mAssignedPackageId); + if (!func(package_group.packages_.front().loaded_package_->GetPackageName(), + package_group.dynamic_ref_table.mAssignedPackageId)) { + return; + } } } @@ -264,10 +283,13 @@ class AssetManager2 { // care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete // and should not be used. // + // When `ignore_configuration` is true, FindEntry will return always select the first entry in + // for the type seen regardless of its configuration. + // // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - FindEntryResult* out_entry) const; + bool ignore_configuration, FindEntryResult* out_entry) const; // Assigns package IDs to all shared library ApkAssets. // Should be called whenever the ApkAssets are changed. @@ -285,6 +307,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_; @@ -338,6 +363,52 @@ class AssetManager2 { // Cached set of bags. These are cached because they can inherit keys from parent bags, // which involves some calculation. std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_; + + // Cached set of bag resid stacks for each bag. These are cached because they might be requested + // a number of times for each view during View inspection. + std::unordered_map<uint32_t, std::vector<uint32_t>> cached_bag_resid_stacks_; + + // Whether or not to save resource resolution steps + bool resource_resolution_logging_enabled_ = false; + + struct Resolution { + + struct Step { + + enum class Type { + INITIAL, + BETTER_MATCH, + OVERLAID + }; + + // Marks what kind of override this step was. + Type type; + + // Built name of configuration for this step. + String8 config_name; + + // Marks the package name of the better resource found in this step. + const std::string* package_name; + }; + + // Last resolved resource ID. + uint32_t resid; + + // Last resolved resource result cookie. + ApkAssetsCookie cookie = kInvalidCookie; + + // Last resolved resource type. + StringPoolRef type_string_ref; + + // Last resolved resource entry. + StringPoolRef entry_string_ref; + + // Steps taken to resolve last resource. + std::vector<Step> steps; + }; + + // Record of the last resolved resource's resolution path. + mutable Resolution last_resolution; }; class Theme { @@ -355,11 +426,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/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h index 35ef98d8c704..d71aad29d917 100644 --- a/libs/androidfw/include/androidfw/AttributeResolution.h +++ b/libs/androidfw/include/androidfw/AttributeResolution.h @@ -23,15 +23,17 @@ namespace android { // Offsets into the outValues array populated by the methods below. outValues is a uint32_t -// array, but each logical element takes up 6 uint32_t-sized physical elements. +// array, but each logical element takes up 7 uint32_t-sized physical elements. +// Keep these in sync with android.content.res.TypedArray java class enum { - STYLE_NUM_ENTRIES = 6, + STYLE_NUM_ENTRIES = 7, STYLE_TYPE = 0, STYLE_DATA = 1, STYLE_ASSET_COOKIE = 2, STYLE_RESOURCE_ID = 3, STYLE_CHANGING_CONFIGURATIONS = 4, - STYLE_DENSITY = 5 + STYLE_DENSITY = 5, + STYLE_SOURCE_RESOURCE_ID = 6 }; // These are all variations of the same method. They each perform the exact same operation, diff --git a/libs/androidfw/include/androidfw/Chunk.h b/libs/androidfw/include/androidfw/Chunk.h index 99a52dc9244e..a0f23433c676 100644 --- a/libs/androidfw/include/androidfw/Chunk.h +++ b/libs/androidfw/include/androidfw/Chunk.h @@ -89,7 +89,9 @@ class ChunkIterator { len_(len), last_error_(nullptr) { CHECK(next_chunk_ != nullptr) << "data can't be nullptr"; - VerifyNextChunk(); + if (len_ != 0) { + VerifyNextChunk(); + } } Chunk Next(); diff --git a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h index bf35aa3c15bb..5381c0174cb0 100644 --- a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h +++ b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h @@ -37,10 +37,14 @@ private: DisplayEventReceiver mReceiver; bool mWaitingForVsync; - virtual void dispatchVsync(nsecs_t timestamp, int32_t id, uint32_t count) = 0; - virtual void dispatchHotplug(nsecs_t timestamp, int32_t id, bool connected) = 0; + virtual void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) = 0; + virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, + bool connected) = 0; + virtual void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId, + int32_t configId) = 0; virtual int handleEvent(int receiveFd, int events, void* data); - bool processPendingEvents(nsecs_t* outTimestamp, int32_t* id, uint32_t* outCount); + bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, + uint32_t* outCount); }; } diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 349b379778a6..950f5413f550 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -20,6 +20,8 @@ #include <memory> #include <set> #include <vector> +#include <unordered_map> +#include <unordered_set> #include "android-base/macros.h" @@ -76,6 +78,12 @@ struct TypeSpec { // TypeSpecPtr is a managed pointer that knows how to delete itself. using TypeSpecPtr = util::unique_cptr<TypeSpec>; +struct OverlayableInfo { + std::string name; + std::string actor; + uint32_t policy_flags; +}; + class LoadedPackage { public: class iterator { @@ -216,6 +224,29 @@ class LoadedPackage { } } + // Retrieves the overlayable properties of the specified resource. If the resource is not + // overlayable, this will return a null pointer. + const OverlayableInfo* GetOverlayableInfo(uint32_t resid) const { + for (const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>& overlayable_info_ids + : overlayable_infos_) { + if (overlayable_info_ids.second.find(resid) != overlayable_info_ids.second.end()) { + return &overlayable_info_ids.first; + } + } + return nullptr; + } + + // Retrieves whether or not the package defines overlayable resources. + // TODO(123905379): Remove this when the enforcement of overlayable is turned on for all APK and + // not just those that defined overlayable resources. + bool DefinesOverlayable() const { + return defines_overlayable_; + } + + const std::unordered_map<std::string, std::string>& GetOverlayableMap() const { + return overlayable_map_; + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); @@ -229,10 +260,13 @@ class LoadedPackage { bool dynamic_ = false; bool system_ = false; bool overlay_ = false; + bool defines_overlayable_ = false; ByteBucketArray<TypeSpecPtr> type_specs_; ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; + std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; + std::unordered_map<std::string, std::string> overlayable_map_; }; // Read-only view into a resource table. This class validates all data diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index be536bf211f5..fc635aaeb0d8 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -234,7 +234,9 @@ enum { RES_TABLE_PACKAGE_TYPE = 0x0200, RES_TABLE_TYPE_TYPE = 0x0201, RES_TABLE_TYPE_SPEC_TYPE = 0x0202, - RES_TABLE_LIBRARY_TYPE = 0x0203 + RES_TABLE_LIBRARY_TYPE = 0x0203, + RES_TABLE_OVERLAYABLE_TYPE = 0x0204, + RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205, }; /** @@ -780,6 +782,9 @@ public: void getPosition(ResXMLPosition* pos) const; void setPosition(const ResXMLPosition& pos); + void setSourceResourceId(const uint32_t resId); + uint32_t getSourceResourceId() const; + private: friend class ResXMLTree; @@ -789,6 +794,7 @@ private: event_code_t mEventCode; const ResXMLTree_node* mCurNode; const void* mCurExt; + uint32_t mSourceResourceId; }; class DynamicRefTable; @@ -1354,10 +1360,6 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, - - // Additional flag indicating an entry is overlayable at runtime. - // Added in Android-P. - SPEC_OVERLAYABLE = 0x80000000u, }; }; @@ -1607,6 +1609,63 @@ struct ResTable_lib_entry uint16_t packageName[128]; }; +/** + * Specifies the set of resources that are explicitly allowed to be overlaid by RROs. + */ +struct ResTable_overlayable_header +{ + struct ResChunk_header header; + + // The name of the overlayable set of resources that overlays target. + uint16_t name[256]; + + // The component responsible for enabling and disabling overlays targeting this chunk. + uint16_t actor[256]; +}; + +/** + * Holds a list of resource ids that are protected from being overlaid by a set of policies. If + * the overlay fulfils at least one of the policies, then the overlay can overlay the list of + * resources. + */ +struct ResTable_overlayable_policy_header +{ + struct ResChunk_header header; + + enum PolicyFlags : uint32_t { + // Any overlay can overlay these resources. + POLICY_PUBLIC = 0x00000001, + + // The overlay must reside of the system partition or must have existed on the system partition + // before an upgrade to overlay these resources. + POLICY_SYSTEM_PARTITION = 0x00000002, + + // The overlay must reside of the vendor partition or must have existed on the vendor partition + // before an upgrade to overlay these resources. + POLICY_VENDOR_PARTITION = 0x00000004, + + // The overlay must reside of the product partition or must have existed on the product + // partition before an upgrade to overlay these resources. + POLICY_PRODUCT_PARTITION = 0x00000008, + + // The overlay must be signed with the same signature as the actor of the target resource, + // which can be separate or the same as the target package with the resource. + POLICY_SIGNATURE = 0x00000010, + + // The overlay must reside of the odm partition or must have existed on the odm + // partition before an upgrade to overlay these resources. + POLICY_ODM_PARTITION = 0x00000020, + + // The overlay must reside of the oem partition or must have existed on the oem + // partition before an upgrade to overlay these resources. + POLICY_OEM_PARTITION = 0x00000040, + }; + uint32_t policy_flags; + + // The number of ResTable_ref that follow this header. + uint32_t entry_count; +}; + struct alignas(uint32_t) Idmap_header { // Always 0x504D4449 ('IDMP') uint32_t magic; @@ -1709,13 +1768,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; }; @@ -1990,7 +2049,7 @@ public: // Return value: on success: NO_ERROR; caller is responsible for free-ing // outData (using free(3)). On failure, any status_t value other than // NO_ERROR; the caller should not free outData. - status_t createIdmap(const ResTable& overlay, + status_t createIdmap(const ResTable& targetResTable, uint32_t targetCrc, uint32_t overlayCrc, const char* targetPath, const char* overlayPath, void** outData, size_t* outSize) const; diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index d94779bf5225..e649940cdde1 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -17,6 +17,7 @@ #ifndef ANDROIDFW_RESOURCEUTILS_H #define ANDROIDFW_RESOURCEUTILS_H +#include "androidfw/AssetManager2.h" #include "androidfw/StringPiece.h" namespace android { @@ -27,6 +28,16 @@ namespace android { bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); +// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. +// Useful for getting resource name without re-running AssetManager2::FindEntry searches. +bool ToResourceName(const StringPoolRef& type_string_ref, + const StringPoolRef& entry_string_ref, + const StringPiece& package_name, + AssetManager2::ResourceName* out_name); + +// Formats a ResourceName to "package:type/entry_name". +std::string ToFormattedResourceString(AssetManager2::ResourceName* resource_name); + inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) { return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24); } diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index c65c86bd064f..921877dc4982 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/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index f1cc569f7d4e..40c8e46e4d84 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -71,6 +71,9 @@ class AssetManager2Test : public ::testing::Test { app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk"); ASSERT_THAT(app_assets_, NotNull()); + + overlayable_assets_ = ApkAssets::Load(GetTestDataPath() + "/overlayable/overlayable.apk"); + ASSERT_THAT(overlayable_assets_, NotNull()); } protected: @@ -83,6 +86,7 @@ class AssetManager2Test : public ::testing::Test { std::unique_ptr<const ApkAssets> appaslib_assets_; std::unique_ptr<const ApkAssets> system_assets_; std::unique_ptr<const ApkAssets> app_assets_; + std::unique_ptr<const ApkAssets> overlayable_assets_; }; TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) { @@ -210,6 +214,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) { EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data); } +TEST_F(AssetManager2Test, GetSharedLibraryResourceName) { + AssetManager2 assetmanager; + assetmanager.SetApkAssets({lib_one_assets_.get()}); + + AssetManager2::ResourceName name; + ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name)); + std::string formatted_name = ToFormattedResourceString(&name); + ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo"); +} + TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) { AssetManager2 assetmanager; assetmanager.SetApkAssets({basic_assets_.get()}); @@ -298,11 +312,13 @@ TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) { EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[0].value.dataType); EXPECT_EQ(1u, bag_two->entries[0].value.data); EXPECT_EQ(0, bag_two->entries[0].cookie); + EXPECT_EQ(app::R::style::StyleOne, bag_two->entries[0].style); // attr_two should be overridden from StyleOne by StyleTwo. EXPECT_EQ(app::R::attr::attr_two, bag_two->entries[1].key); EXPECT_EQ(Res_value::TYPE_STRING, bag_two->entries[1].value.dataType); EXPECT_EQ(0, bag_two->entries[1].cookie); + EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[1].style); EXPECT_EQ(std::string("string"), GetStringFromPool(assetmanager.GetStringPoolForCookie(0), bag_two->entries[1].value.data)); @@ -312,21 +328,25 @@ TEST_F(AssetManager2Test, MergesStylesWithParentFromSingleApkAssets) { EXPECT_EQ(Res_value::TYPE_ATTRIBUTE, bag_two->entries[2].value.dataType); EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[2].value.data); EXPECT_EQ(0, bag_two->entries[2].cookie); + EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[2].style); EXPECT_EQ(app::R::attr::attr_five, bag_two->entries[3].key); EXPECT_EQ(Res_value::TYPE_REFERENCE, bag_two->entries[3].value.dataType); EXPECT_EQ(app::R::string::string_one, bag_two->entries[3].value.data); EXPECT_EQ(0, bag_two->entries[3].cookie); + EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[3].style); EXPECT_EQ(app::R::attr::attr_indirect, bag_two->entries[4].key); EXPECT_EQ(Res_value::TYPE_INT_DEC, bag_two->entries[4].value.dataType); EXPECT_EQ(3u, bag_two->entries[4].value.data); EXPECT_EQ(0, bag_two->entries[4].cookie); + EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[4].style); EXPECT_EQ(app::R::attr::attr_empty, bag_two->entries[5].key); EXPECT_EQ(Res_value::TYPE_NULL, bag_two->entries[5].value.dataType); EXPECT_EQ(Res_value::DATA_NULL_EMPTY, bag_two->entries[5].value.data); EXPECT_EQ(0, bag_two->entries[5].cookie); + EXPECT_EQ(app::R::style::StyleTwo, bag_two->entries[5].style); } TEST_F(AssetManager2Test, MergeStylesCircularDependency) { @@ -580,4 +600,127 @@ TEST_F(AssetManager2Test, OpenDirFromManyApks) { EXPECT_THAT(asset_dir->getFileType(2), Eq(FileType::kFileTypeDirectory)); } +TEST_F(AssetManager2Test, GetLastPathWithoutEnablingReturnsEmpty) { + ResTable_config desired_config; + + AssetManager2 assetmanager; + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({basic_assets_.get()}); + assetmanager.SetResourceResolutionLoggingEnabled(false); + + Res_value value; + ResTable_config selected_config; + uint32_t flags; + + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + auto result = assetmanager.GetLastResourceResolution(); + EXPECT_EQ("", result); +} + +TEST_F(AssetManager2Test, GetLastPathWithoutResolutionReturnsEmpty) { + ResTable_config desired_config; + + AssetManager2 assetmanager; + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({basic_assets_.get()}); + + auto result = assetmanager.GetLastResourceResolution(); + EXPECT_EQ("", result); +} + +TEST_F(AssetManager2Test, GetLastPathWithSingleApkAssets) { + ResTable_config desired_config; + memset(&desired_config, 0, sizeof(desired_config)); + desired_config.language[0] = 'd'; + desired_config.language[1] = 'e'; + + AssetManager2 assetmanager; + assetmanager.SetResourceResolutionLoggingEnabled(true); + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({basic_assets_.get()}); + + Res_value value; + ResTable_config selected_config; + uint32_t flags; + + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + auto result = assetmanager.GetLastResourceResolution(); + EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n\tFor config -de\n\tFound initial: com.android.basic", result); +} + +TEST_F(AssetManager2Test, GetLastPathWithMultipleApkAssets) { + ResTable_config desired_config; + memset(&desired_config, 0, sizeof(desired_config)); + desired_config.language[0] = 'd'; + desired_config.language[1] = 'e'; + + AssetManager2 assetmanager; + assetmanager.SetResourceResolutionLoggingEnabled(true); + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({basic_assets_.get(), basic_de_fr_assets_.get()}); + + Res_value value = Res_value(); + ResTable_config selected_config; + uint32_t flags; + + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + auto result = assetmanager.GetLastResourceResolution(); + EXPECT_EQ("Resolution for 0x7f030000 com.android.basic:string/test1\n\tFor config -de\n\tFound initial: com.android.basic\n\tFound better: com.android.basic -de", result); +} + +TEST_F(AssetManager2Test, GetLastPathAfterDisablingReturnsEmpty) { + ResTable_config desired_config; + memset(&desired_config, 0, sizeof(desired_config)); + + AssetManager2 assetmanager; + assetmanager.SetResourceResolutionLoggingEnabled(true); + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({basic_assets_.get()}); + + Res_value value = Res_value(); + ResTable_config selected_config; + uint32_t flags; + + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::string::test1, false /*may_be_bag*/, + 0 /*density_override*/, &value, &selected_config, &flags); + ASSERT_NE(kInvalidCookie, cookie); + + auto resultEnabled = assetmanager.GetLastResourceResolution(); + ASSERT_NE("", resultEnabled); + + assetmanager.SetResourceResolutionLoggingEnabled(false); + + auto resultDisabled = assetmanager.GetLastResourceResolution(); + EXPECT_EQ("", resultDisabled); +} + +TEST_F(AssetManager2Test, GetOverlayableMap) { + ResTable_config desired_config; + memset(&desired_config, 0, sizeof(desired_config)); + + AssetManager2 assetmanager; + assetmanager.SetResourceResolutionLoggingEnabled(true); + assetmanager.SetConfiguration(desired_config); + assetmanager.SetApkAssets({overlayable_assets_.get()}); + + const auto map = assetmanager.GetOverlayableMapForPackage(0x7f); + ASSERT_NE(nullptr, map); + ASSERT_EQ(2, map->size()); + ASSERT_EQ(map->at("OverlayableResources1"), "overlay://theme"); + ASSERT_EQ(map->at("OverlayableResources2"), "overlay://com.android.overlayable"); +} + } // namespace android 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..10b83a75304d 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -40,7 +40,7 @@ class IdmapTest : public ::testing::Test { ASSERT_EQ(NO_ERROR, overlay_table.add(overlay_data_.data(), overlay_data_.size())); char target_name[256] = "com.android.basic"; - ASSERT_EQ(NO_ERROR, target_table_.createIdmap(overlay_table, 0, 0, target_name, target_name, + ASSERT_EQ(NO_ERROR, overlay_table.createIdmap(target_table_, 0, 0, target_name, target_name, &data_, &data_size_)); } @@ -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/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index ffa48367c252..d58e8d20c8aa 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -22,12 +22,14 @@ #include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" +#include "data/overlayable/R.h" #include "data/sparse/R.h" #include "data/styles/R.h" namespace app = com::android::app; namespace basic = com::android::basic; namespace libclient = com::android::libclient; +namespace overlayable = com::android::overlayable; namespace sparse = com::android::sparse; using ::android::base::ReadFileToString; @@ -273,10 +275,51 @@ TEST(LoadedArscTest, LoadOverlay) { ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } -// structs with size fields (like Res_value, ResTable_entry) should be -// backwards and forwards compatible (aka checking the size field against -// sizeof(Res_value) might not be backwards compatible. -TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } +TEST(LoadedArscTest, LoadOverlayable) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlayable/overlayable.apk", + "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = + LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, + false /*load_as_shared_library*/); + + ASSERT_THAT(loaded_arsc, NotNull()); + const LoadedPackage* package = loaded_arsc->GetPackageById( + get_package_id(overlayable::R::string::not_overlayable)); + + const OverlayableInfo* info = package->GetOverlayableInfo( + overlayable::R::string::not_overlayable); + ASSERT_THAT(info, IsNull()); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable1); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->name, Eq("OverlayableResources1")); + EXPECT_THAT(info->actor, Eq("overlay://theme")); + EXPECT_THAT(info->policy_flags, Eq(ResTable_overlayable_policy_header::POLICY_PUBLIC)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable2); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->name, Eq("OverlayableResources1")); + EXPECT_THAT(info->actor, Eq("overlay://theme")); + EXPECT_THAT(info->policy_flags, + Eq(ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION + | ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable3); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->name, Eq("OverlayableResources2")); + EXPECT_THAT(info->actor, Eq("overlay://com.android.overlayable")); + EXPECT_THAT(info->policy_flags, + Eq(ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION + | ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable4); + EXPECT_THAT(info->name, Eq("OverlayableResources1")); + EXPECT_THAT(info->actor, Eq("overlay://theme")); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->policy_flags, Eq(ResTable_overlayable_policy_header::POLICY_PUBLIC)); +} TEST(LoadedArscTest, ResourceIdentifierIterator) { std::string contents; @@ -288,7 +331,7 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) { const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); ASSERT_EQ(1u, packages.size()); - EXPECT_EQ(std::string("com.android.basic"), packages[0]->GetPackageName()); + ASSERT_EQ(std::string("com.android.basic"), packages[0]->GetPackageName()); const auto& loaded_package = packages[0]; auto iter = loaded_package->begin(); @@ -326,4 +369,27 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) { ASSERT_EQ(end, iter); } +TEST(LoadedArscTest, GetOverlayableMap) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlayable/overlayable.apk", + "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_NE(nullptr, loaded_arsc); + + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + ASSERT_EQ(std::string("com.android.overlayable"), packages[0]->GetPackageName()); + + const auto map = packages[0]->GetOverlayableMap(); + ASSERT_EQ(2, map.size()); + ASSERT_EQ(map.at("OverlayableResources1"), "overlay://theme"); + ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable"); +} + +// structs with size fields (like Res_value, ResTable_entry) should be +// backwards and forwards compatible (aka checking the size field against +// sizeof(Res_value) might not be backwards compatible. +// TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } + } // namespace android diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index 55d53edf6a2b..be5ecd94a588 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,30 +281,82 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); } -TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { - AssetManager2 assetmanager_one; - assetmanager_one.SetApkAssets({system_assets_.get(), style_assets_.get()}); +TEST_F(ThemeTest, OnlyCopySameAssetsThemeWhenAssetManagersDiffer) { + AssetManager2 assetmanager_dst; + assetmanager_dst.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()}); + AssetManager2 assetmanager_src; + assetmanager_src.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)); + auto theme_dst = assetmanager_dst.NewTheme(); + ASSERT_TRUE(theme_dst->ApplyStyle(app::R::style::StyleOne)); - auto theme_two = assetmanager_two.NewTheme(); - ASSERT_TRUE(theme_two->ApplyStyle(R::style::Theme_One)); - ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo)); + auto theme_src = assetmanager_src.NewTheme(); + ASSERT_TRUE(theme_src->ApplyStyle(R::style::Theme_One)); + ASSERT_TRUE(theme_src->ApplyStyle(app::R::style::StyleTwo)); + ASSERT_TRUE(theme_src->ApplyStyle(fix_package_id(lib_one::R::style::Theme, 0x03), + false /*force*/)); + ASSERT_TRUE(theme_src->ApplyStyle(fix_package_id(lib_two::R::style::Theme, 0x02), + false /*force*/)); - EXPECT_TRUE(theme_one->SetTo(*theme_two)); + theme_dst->SetTo(*theme_src); 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_dst->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_dst->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_dst->GetAttribute(fix_package_id(lib_one::R::attr::attr1, 0x02), &value, + &flags)); + EXPECT_EQ(1, theme_dst->GetAttribute(fix_package_id(lib_one::R::attr::attr2, 0x02), &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); +} + +TEST_F(ThemeTest, CopyNonReferencesWhenPackagesDiffer) { + AssetManager2 assetmanager_dst; + assetmanager_dst.SetApkAssets({system_assets_.get()}); + + AssetManager2 assetmanager_src; + assetmanager_src.SetApkAssets({system_assets_.get(), style_assets_.get()}); + + auto theme_dst = assetmanager_dst.NewTheme(); + auto theme_src = assetmanager_src.NewTheme(); + ASSERT_TRUE(theme_src->ApplyStyle(app::R::style::StyleSeven)); + theme_dst->SetTo(*theme_src); + + Res_value value; + uint32_t flags; + + // Allow inline resource values to be copied even if the source apk asset is not present in the + // destination. + EXPECT_EQ(0, theme_dst->GetAttribute(0x0101021b /* android:versionCode */, &value, &flags)); + + // Do not copy strings since the data is an index into the values string pool of the source apk + // asset. + EXPECT_EQ(-1, theme_dst->GetAttribute(0x01010001 /* android:label */, &value, &flags)); + + // Do not copy values that reference another resource if the resource is not present in the + // destination. + EXPECT_EQ(-1, theme_dst->GetAttribute(0x01010002 /* android:icon */, &value, &flags)); + EXPECT_EQ(-1, theme_dst->GetAttribute(0x010100d1 /* android:tag */, &value, &flags)); - // Only system. - EXPECT_NE(kInvalidCookie, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); + // Allow @empty to and @null to be copied. + EXPECT_EQ(0, theme_dst->GetAttribute(0x010100d0 /* android:id */, &value, &flags)); + EXPECT_EQ(0, theme_dst->GetAttribute(0x01010000 /* android:theme */, &value, &flags)); } } // 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/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk Binary files differindex 33f961117c44..d37874dcbb40 100644 --- a/libs/androidfw/tests/data/overlay/overlay.apk +++ b/libs/androidfw/tests/data/overlay/overlay.apk diff --git a/libs/androidfw/tests/data/overlayable/AndroidManifest.xml b/libs/androidfw/tests/data/overlayable/AndroidManifest.xml new file mode 100644 index 000000000000..abc2a454e845 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlayable"> + <application> + </application> +</manifest> diff --git a/libs/androidfw/tests/data/overlayable/R.h b/libs/androidfw/tests/data/overlayable/R.h new file mode 100644 index 000000000000..e46e264da318 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/R.h @@ -0,0 +1,42 @@ +/* + * 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 TESTS_DATA_OVERLAYABLE_R_H_ +#define TESTS_DATA_OVERLAYABLE_R_H_ + +#include <cstdint> + +namespace com { +namespace android { +namespace overlayable { + +struct R { + struct string { + enum : uint32_t { + not_overlayable = 0x7f010000, + overlayable1 = 0x7f010001, + overlayable2 = 0x7f010002, + overlayable3 = 0x7f010003, + overlayable4 = 0x7f010004, + }; + }; +}; + +} // namespace overlayable +} // namespace android +} // namespace com + +#endif /* TESTS_DATA_OVERLAYABLE_R_H_ */ diff --git a/libs/androidfw/tests/data/overlayable/build b/libs/androidfw/tests/data/overlayable/build new file mode 100755 index 000000000000..98fdc5101160 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/build @@ -0,0 +1,22 @@ +#!/bin/bash +# +# 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. +# + +set -e + +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -o overlayable.apk compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/overlayable/overlayable.apk b/libs/androidfw/tests/data/overlayable/overlayable.apk Binary files differnew file mode 100644 index 000000000000..047e6afde86b --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/overlayable.apk diff --git a/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml b/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml new file mode 100644 index 000000000000..fcdbe94466c1 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> +<overlayable name="OverlayableResources1" actor="overlay://theme"> + <!-- Any overlay can overlay the value of @string/overlayable1 --> + <item type="string" name="overlayable1" /> + + <!-- Any overlay on the product or system partition can overlay the value of + @string/overlayable2 --> + <policy type="product|system"> + <item type="string" name="overlayable2" /> + </policy> + + <!-- Any overlay can overlay the value of @string/overlayable4 --> + <policy type="public"> + <item type="string" name="overlayable4" /> + </policy> +</overlayable> + +<overlayable name="OverlayableResources2" actor="overlay://com.android.overlayable"> + <!-- Any overlay on the vendor or product partition can overlay the value of + @string/overlayable3 --> + <policy type="vendor|product"> + <item type="string" name="overlayable3" /> + </policy> +</overlayable> +</resources>
\ No newline at end of file diff --git a/libs/androidfw/tests/data/overlayable/res/values/public.xml b/libs/androidfw/tests/data/overlayable/res/values/public.xml new file mode 100644 index 000000000000..5676d7cc64c9 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/public.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <public type="string" name="not_overlayable" id="0x7f010000" /> + <public type="string" name="overlayable1" id="0x7f010001" /> + <public type="string" name="overlayable2" id="0x7f010002" /> + <public type="string" name="overlayable3" id="0x7f010003" /> + <public type="string" name="overlayable4" id="0x7f010004" /> +</resources>
\ No newline at end of file diff --git a/libs/androidfw/tests/data/overlayable/res/values/values.xml b/libs/androidfw/tests/data/overlayable/res/values/values.xml new file mode 100644 index 000000000000..a86b31282bc9 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/values.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources> + <string name="not_overlayable">Not overlayable</string> + <string name="overlayable1">Overlayable One</string> + <string name="overlayable2">Overlayable Two</string> + <string name="overlayable3">Overlayable Three</string> + <string name="overlayable4">Overlayable Four</string> +</resources> diff --git a/libs/androidfw/tests/data/styles/R.h b/libs/androidfw/tests/data/styles/R.h index 538a84717176..f11486fe924a 100644 --- a/libs/androidfw/tests/data/styles/R.h +++ b/libs/androidfw/tests/data/styles/R.h @@ -51,6 +51,7 @@ struct R { StyleFour = 0x7f020003u, StyleFive = 0x7f020004u, StyleSix = 0x7f020005u, + StyleSeven = 0x7f020006u, }; }; }; diff --git a/libs/androidfw/tests/data/styles/build b/libs/androidfw/tests/data/styles/build index 1ef8e6e19208..7b7c1f7aa962 100755 --- a/libs/androidfw/tests/data/styles/build +++ b/libs/androidfw/tests/data/styles/build @@ -2,5 +2,7 @@ set -e +PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar + aapt2 compile -o compiled.flata --dir res -aapt2 link -o styles.apk --manifest AndroidManifest.xml compiled.flata +aapt2 link -o styles.apk --manifest AndroidManifest.xml -I $PATH_TO_FRAMEWORK_RES compiled.flata diff --git a/libs/androidfw/tests/data/styles/res/values/styles.xml b/libs/androidfw/tests/data/styles/res/values/styles.xml index 1a231768dade..06774a8a6005 100644 --- a/libs/androidfw/tests/data/styles/res/values/styles.xml +++ b/libs/androidfw/tests/data/styles/res/values/styles.xml @@ -79,4 +79,14 @@ <item name="attr_three">3</item> </style> + <public type="style" name="StyleSeven" id="0x7f020006" /> + <style name="StyleSeven" > + <item name="android:versionCode">3</item> + <item name="android:label">"string"</item> + <item name="android:icon">?attr/attr_one</item> + <item name="android:tag">@string/string_one</item> + <item name="android:id">@null</item> + <item name="android:theme">@empty</item> + </style> + </resources> diff --git a/libs/androidfw/tests/data/styles/styles.apk b/libs/androidfw/tests/data/styles/styles.apk Binary files differindex cd5c7a1c6c12..92e9bf90101e 100644 --- a/libs/androidfw/tests/data/styles/styles.apk +++ b/libs/androidfw/tests/data/styles/styles.apk 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 0a5b56b30e66..ebba4cb79dfb 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -9,6 +9,8 @@ cc_defaults { "hwui_lto", ], + cpp_std: "experimental", + cflags: [ "-DEGL_EGLEXT_PROTOTYPES", "-DGL_GLEXT_PROTOTYPES", @@ -26,10 +28,6 @@ cc_defaults { // clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629 "-Wno-missing-braces", - - // TODO: Linear blending should be enabled by default, but we are - // TODO: making it an opt-in while it's a work in progress - //"-DANDROID_ENABLE_LINEAR_BLENDING", ], include_dirs: [ @@ -56,9 +54,12 @@ cc_defaults { shared_libs: [ "liblog", "libcutils", + "libstatslog", "libutils", "libEGL", + "libGLESv1_CM", "libGLESv2", + "libGLESv3", "libvulkan", "libui", "libgui", @@ -67,7 +68,8 @@ cc_defaults { "libft2", "libminikin", "libandroidfw", - "libRScpp", + "libcrypto", + "libsync", ], static_libs: [ "libEGL_blobCache", @@ -76,7 +78,6 @@ cc_defaults { cc_defaults { name: "hwui_bugreport_font_cache_usage", - srcs: ["font/FontCacheHistoryTracker.cpp"], cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"], } @@ -153,8 +154,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", @@ -168,115 +167,79 @@ 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/VkFunctorDrawable.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/ReliableSurface.cpp", "renderthread/VulkanManager.cpp", + "renderthread/VulkanSurface.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", "renderthread/RenderThread.cpp", "renderthread/TimeLord.cpp", "renderthread/Frame.cpp", "service/GraphicsStatsService.cpp", - "thread/TaskManager.cpp", + "surfacetexture/EGLConsumer.cpp", + "surfacetexture/ImageConsumer.cpp", + "surfacetexture/SurfaceTexture.cpp", + "thread/CommonPool.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", + "HWUIProperties.sysprop", "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", + "WebViewFunctorManager.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 { @@ -287,7 +250,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", ], } @@ -343,50 +306,35 @@ 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/CommonPoolTests.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/RenderThreadTests.cpp", "tests/unit/ShaderCacheTests.cpp", "tests/unit/SkiaBehaviorTests.cpp", "tests/unit/SkiaDisplayListTests.cpp", "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", "tests/unit/VectorDrawableAtlasTests.cpp", + "tests/unit/WebViewFunctorManagerTests.cpp", ], } @@ -431,13 +379,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..0cfaa8c61279 --- /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 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..3bee3018d36e 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,51 @@ 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; + 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, &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, 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, + const sk_sp<SkImage>& layerImage) { mLayer->setBlend(mBlend); mLayer->setForceFilter(forceFilter); mLayer->setSize(mWidth, mHeight); - mLayer->getTexTransform().load(textureTransform); - mLayer->setDataSpace(dataspace); + mLayer->getTexTransform() = textureTransform; + mLayer->setImage(layerImage); } void DeferredLayerUpdater::detachSurfaceTexture() { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index fe3ee7a2b4c6..a91c111933c4 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, + 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..0a9d965d0444 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -20,18 +20,17 @@ #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> +#include <ui/GraphicTypes.h> #include <mutex> #include <thread> #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,46 +41,110 @@ 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; +DeviceInfo* DeviceInfo::get() { + static DeviceInfo sDeviceInfo; + return &sDeviceInfo; } -void DeviceInfo::initialize() { - std::call_once(sInitializedFlag, []() { - sDeviceInfo = new DeviceInfo(); - sDeviceInfo->load(); - }); -} +static DisplayInfo QueryDisplayInfo() { + if (Properties::isolatedProcess) { + return sDummyDisplay; + } + + const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken(); + LOG_ALWAYS_FATAL_IF(token == nullptr, + "Failed to get display info because internal display is disconnected"); -void DeviceInfo::initialize(int maxTextureSize) { - std::call_once(sInitializedFlag, [maxTextureSize]() { - sDeviceInfo = new DeviceInfo(); - sDeviceInfo->mDisplayInfo = DeviceInfo::queryDisplayInfo(); - sDeviceInfo->mMaxTextureSize = maxTextureSize; - }); + DisplayInfo displayInfo; + status_t status = SurfaceComposerClient::getDisplayInfo(token, &displayInfo); + LOG_ALWAYS_FATAL_IF(status, "Failed to get display info, error %d", status); + return displayInfo; } -void DeviceInfo::load() { - mDisplayInfo = queryDisplayInfo(); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); +static float QueryMaxRefreshRate() { + if (Properties::isolatedProcess) { + return sDummyDisplay.fps; + } + + const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken(); + LOG_ALWAYS_FATAL_IF(token == nullptr, + "Failed to get display info because internal display is disconnected"); + + Vector<DisplayInfo> configs; + configs.reserve(10); + status_t status = SurfaceComposerClient::getDisplayConfigs(token, &configs); + LOG_ALWAYS_FATAL_IF(status, "Failed to getDisplayConfigs, error %d", status); + LOG_ALWAYS_FATAL_IF(configs.size() == 0, "getDisplayConfigs returned 0 configs?"); + float max = 0.0f; + for (auto& info : configs) { + max = std::max(max, info.fps); + } + return max; } -DisplayInfo DeviceInfo::queryDisplayInfo() { +static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) { if (Properties::isolatedProcess) { - return sDummyDisplay; + *colorSpace = SkColorSpace::MakeSRGB(); + *colorType = SkColorType::kN32_SkColorType; + return; + } + ui::Dataspace defaultDataspace, wcgDataspace; + ui::PixelFormat defaultPixelFormat, wcgPixelFormat; + status_t status = + SurfaceComposerClient::getCompositionPreference(&defaultDataspace, &defaultPixelFormat, + &wcgDataspace, &wcgPixelFormat); + LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status); + switch (wcgDataspace) { + case ui::Dataspace::DISPLAY_P3: + *colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); + break; + case ui::Dataspace::V0_SCRGB: + *colorSpace = SkColorSpace::MakeSRGB(); + break; + case ui::Dataspace::V0_SRGB: + // when sRGB is returned, it means wide color gamut is not supported. + *colorSpace = SkColorSpace::MakeSRGB(); + break; + default: + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); } + switch (wcgPixelFormat) { + case ui::PixelFormat::RGBA_8888: + *colorType = SkColorType::kN32_SkColorType; + break; + case ui::PixelFormat::RGBA_FP16: + *colorType = SkColorType::kRGBA_F16_SkColorType; + break; + default: + LOG_ALWAYS_FATAL("Unreachable: unsupported pixel format."); + } +} - DisplayInfo displayInfo; - sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); - status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &displayInfo); - LOG_ALWAYS_FATAL_IF(status, "Failed to get display info, error %d", status); - return displayInfo; +DeviceInfo::DeviceInfo() : mMaxRefreshRate(QueryMaxRefreshRate()) { +#if HWUI_NULL_GPU + mMaxTextureSize = NULL_GPU_MAX_TEXTURE_SIZE; +#else + mMaxTextureSize = -1; +#endif + mDisplayInfo = QueryDisplayInfo(); + queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType); +} + +int DeviceInfo::maxTextureSize() const { + LOG_ALWAYS_FATAL_IF(mMaxTextureSize < 0, "MaxTextureSize has not been initialized yet."); + return mMaxTextureSize; +} + +void DeviceInfo::setMaxTextureSize(int maxTextureSize) { + DeviceInfo::get()->mMaxTextureSize = maxTextureSize; +} + +void DeviceInfo::onDisplayConfigChanged() { + mDisplayInfo = QueryDisplayInfo(); } } /* namespace uirenderer */ diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 297b2664414b..0e3f11960ddc 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -16,48 +16,45 @@ #ifndef DEVICEINFO_H #define DEVICEINFO_H +#include <SkImageInfo.h> #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); + static DeviceInfo* get(); - 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; - } + sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } + SkColorType getWideColorType() const { return mWideColorType; } + float getMaxRefreshRate() const { return mMaxRefreshRate; } - static DisplayInfo queryDisplayInfo(); + void onDisplayConfigChanged(); private: - DeviceInfo() {} - ~DeviceInfo() {} + friend class renderthread::RenderThread; + static void setMaxTextureSize(int maxTextureSize); - void load(); + DeviceInfo(); int mMaxTextureSize; DisplayInfo mDisplayInfo; - Extensions mExtensions; + sk_sp<SkColorSpace> mWideColorSpace; + SkColorType mWideColorType; + const float mMaxRefreshRate; }; } /* 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..dc63e5db4a70 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 +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in new file mode 100644 index 000000000000..2deb5657c877 --- /dev/null +++ b/libs/hwui/DisplayListOps.in @@ -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. + */ + +X(Flush) +X(Save) +X(Restore) +X(SaveLayer) +X(SaveBehind) +X(Concat) +X(SetMatrix) +X(Translate) +X(ClipPath) +X(ClipRect) +X(ClipRRect) +X(ClipRegion) +X(DrawPaint) +X(DrawBehind) +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(DrawTextBlob) +X(DrawPatch) +X(DrawPoints) +X(DrawVertices) +X(DrawAtlas) +X(DrawShadowRec) +X(DrawVectorDrawable) 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..3a8e559f6d7e 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -15,9 +15,9 @@ */ #include "FrameInfoVisualizer.h" -#include "BakedOpRenderer.h" #include "IProfileRenderer.h" #include "utils/Color.h" +#include "utils/TimeUtils.h" #include <cutils/compiler.h> #include <array> @@ -27,22 +27,24 @@ #define RETURN_IF_DISABLED() \ if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return -#define PROFILE_DRAW_WIDTH 3 -#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 -#define PROFILE_DRAW_DP_PER_MS 7 - namespace android { namespace uirenderer { -// Must be NUM_ELEMENTS in size -static const SkColor THRESHOLD_COLOR = Color::Green_500; -static const SkColor BAR_FAST_MASK = 0x8FFFFFFF; -static const SkColor BAR_JANKY_MASK = 0xDFFFFFFF; +static constexpr auto PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; +static constexpr auto PROFILE_DRAW_DP_PER_MS = 7; + +struct Threshold { + SkColor color; + float percentFrametime; +}; -// We could get this from TimeLord and use the actual frame interval, but -// this is good enough -#define FRAME_THRESHOLD 16 -#define FRAME_THRESHOLD_NS 16000000 +static constexpr std::array<Threshold, 3> THRESHOLDS{ + Threshold{.color = Color::Green_500, .percentFrametime = 0.8f}, + Threshold{.color = Color::Lime_500, .percentFrametime = 1.0f}, + Threshold{.color = Color::Red_500, .percentFrametime = 1.5f}, +}; +static constexpr SkColor BAR_FAST_MASK = 0x8FFFFFFF; +static constexpr SkColor BAR_JANKY_MASK = 0xDFFFFFFF; struct BarSegment { FrameInfoIndex start; @@ -65,8 +67,10 @@ static int dpToPx(int dp, float density) { return (int)(dp * density + 0.5f); } -FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source) : mFrameSource(source) { +FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source, nsecs_t frameInterval) + : mFrameSource(source), mFrameInterval(frameInterval) { setDensity(1); + consumeProperties(); } FrameInfoVisualizer::~FrameInfoVisualizer() { @@ -76,7 +80,10 @@ FrameInfoVisualizer::~FrameInfoVisualizer() { void FrameInfoVisualizer::setDensity(float density) { if (CC_UNLIKELY(mDensity != density)) { mDensity = density; - mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); + // We want the vertical units to scale height relative to a baseline 16ms. + // This keeps the threshold lines consistent across varying refresh rates + mVerticalUnit = static_cast<int>(dpToPx(PROFILE_DRAW_DP_PER_MS, density) * (float)16_ms / + (float)mFrameInterval); mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); } } @@ -148,7 +155,7 @@ void FrameInfoVisualizer::initializeRects(const int baseline, const int width) { float* rect; int ri; // Rects are LTRB - if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) { + if (mFrameSource[fi].totalDuration() <= mFrameInterval) { rect = mFastRects.get(); ri = fast_i; fast_i += 4; @@ -181,7 +188,7 @@ void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex en float* rect; int ri; // Rects are LTRB - if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) { + if (mFrameSource[fi].totalDuration() <= mFrameInterval) { rect = mFastRects.get(); ri = fast_i; fast_i -= 4; @@ -211,10 +218,13 @@ void FrameInfoVisualizer::drawGraph(IProfileRenderer& renderer) { void FrameInfoVisualizer::drawThreshold(IProfileRenderer& renderer) { SkPaint paint; - paint.setColor(THRESHOLD_COLOR); - float yLocation = renderer.getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); - renderer.drawRect(0.0f, yLocation - mThresholdStroke / 2, renderer.getViewportWidth(), - yLocation + mThresholdStroke / 2, paint); + for (auto& t : THRESHOLDS) { + paint.setColor(t.color); + float yLocation = renderer.getViewportHeight() - + (ns2ms(mFrameInterval) * t.percentFrametime * mVerticalUnit); + renderer.drawRect(0.0f, yLocation - mThresholdStroke / 2, renderer.getViewportWidth(), + yLocation + mThresholdStroke / 2, paint); + } } bool FrameInfoVisualizer::consumeProperties() { diff --git a/libs/hwui/FrameInfoVisualizer.h b/libs/hwui/FrameInfoVisualizer.h index b98f50101483..3040a77ef0d4 100644 --- a/libs/hwui/FrameInfoVisualizer.h +++ b/libs/hwui/FrameInfoVisualizer.h @@ -39,7 +39,7 @@ typedef RingBuffer<FrameInfo, 120> FrameInfoSource; class FrameInfoVisualizer { public: - explicit FrameInfoVisualizer(FrameInfoSource& source); + explicit FrameInfoVisualizer(FrameInfoSource& source, nsecs_t frameInterval); ~FrameInfoVisualizer(); bool consumeProperties(); @@ -71,6 +71,7 @@ private: FrameInfoSource& mFrameSource; + nsecs_t mFrameInterval; int mVerticalUnit = 0; int mThresholdStroke = 0; diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h index 0b9ae5cb4b7b..b93f07853242 100644 --- a/libs/hwui/FrameMetricsObserver.h +++ b/libs/hwui/FrameMetricsObserver.h @@ -26,5 +26,5 @@ public: virtual void notify(const int64_t* buffer) = 0; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h index d920a99f5ee3..75b8038c5040 100644 --- a/libs/hwui/FrameMetricsReporter.h +++ b/libs/hwui/FrameMetricsReporter.h @@ -56,5 +56,5 @@ private: std::vector<sp<FrameMetricsObserver> > mObservers; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android 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/GlFunctorLifecycleListener.h b/libs/hwui/GlFunctorLifecycleListener.h index 5d07b46919d4..5adc46961c8b 100644 --- a/libs/hwui/GlFunctorLifecycleListener.h +++ b/libs/hwui/GlFunctorLifecycleListener.h @@ -28,5 +28,5 @@ public: virtual void onGlFunctorReleased(Functor* functor) = 0; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android 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/HWUIProperties.sysprop b/libs/hwui/HWUIProperties.sysprop new file mode 100644 index 000000000000..34aeaae867f9 --- /dev/null +++ b/libs/hwui/HWUIProperties.sysprop @@ -0,0 +1,16 @@ +owner: Platform +module: "android.uirenderer" +prop { + api_name: "use_vulkan" + type: Boolean + prop_name: "ro.hwui.use_vulkan" + scope: Public + access: Readonly +} +prop { + api_name: "render_ahead" + type: Integer + prop_name: "ro.hwui.render_ahead" + scope: Public + access: Readonly +}
\ No newline at end of file diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp new file mode 100644 index 000000000000..9bb6031b76ac --- /dev/null +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -0,0 +1,423 @@ +/* + * 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 "renderthread/VulkanManager.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 <GrContext.h> +#include <SkCanvas.h> +#include <SkImage.h> +#include <utils/GLUtils.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <thread> + +namespace android::uirenderer { + +class AHBUploader; +// This helper uploader classes allows us to upload using either EGL or Vulkan using the same +// interface. +static sp<AHBUploader> sUploader = nullptr; + +struct FormatInfo { + PixelFormat pixelFormat; + GLint format, type; + VkFormat vkFormat; + bool isSupported = false; + bool valid = true; +}; + +class AHBUploader : public RefBase { +public: + virtual ~AHBUploader() {} + + // Called to start creation of the Vulkan and EGL contexts on another thread before we actually + // need to do an upload. + void initialize() { + onInitialize(); + } + + void destroy() { + std::lock_guard _lock{mLock}; + LOG_ALWAYS_FATAL_IF(mPendingUploads, "terminate called while uploads in progress"); + if (mUploadThread) { + mUploadThread->requestExit(); + mUploadThread->join(); + mUploadThread = nullptr; + } + onDestroy(); + } + + bool uploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, + sp<GraphicBuffer> graphicBuffer) { + ATRACE_CALL(); + beginUpload(); + bool result = onUploadHardwareBitmap(bitmap, format, graphicBuffer); + endUpload(); + return result; + } + + void postIdleTimeoutCheck() { + mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); }); + } + +protected: + std::mutex mLock; + sp<ThreadBase> mUploadThread = nullptr; + +private: + virtual void onInitialize() = 0; + virtual void onIdle() = 0; + virtual void onDestroy() = 0; + + virtual bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, + sp<GraphicBuffer> graphicBuffer) = 0; + virtual void onBeginUpload() = 0; + + bool shouldTimeOutLocked() { + nsecs_t durationSince = systemTime() - mLastUpload; + return durationSince > 2000_ms; + } + + void idleTimeoutCheck() { + std::lock_guard _lock{mLock}; + if (mPendingUploads == 0 && shouldTimeOutLocked()) { + onIdle(); + } else { + this->postIdleTimeoutCheck(); + } + } + + void beginUpload() { + std::lock_guard _lock{mLock}; + mPendingUploads++; + + if (!mUploadThread) { + mUploadThread = new ThreadBase{}; + } + if (!mUploadThread->isRunning()) { + mUploadThread->start("GrallocUploadThread"); + } + + onBeginUpload(); + } + + void endUpload() { + std::lock_guard _lock{mLock}; + mPendingUploads--; + mLastUpload = systemTime(); + } + + int mPendingUploads = 0; + nsecs_t mLastUpload = 0; +}; + +#define FENCE_TIMEOUT 2000000000 + +class EGLUploader : public AHBUploader { +private: + void onInitialize() override {} + void onDestroy() override { + mEglManager.destroy(); + } + void onIdle() override { + mEglManager.destroy(); + } + + void onBeginUpload() override { + if (!mEglManager.hasEglContext()) { + mUploadThread->queue().runSync([this]() { + this->mEglManager.initialize(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + }); + + this->postIdleTimeoutCheck(); + } + } + + + EGLDisplay getUploadEglDisplay() { + std::lock_guard _lock{mLock}; + LOG_ALWAYS_FATAL_IF(!mEglManager.hasEglContext(), "Forgot to begin an upload?"); + return mEglManager.eglDisplay(); + } + + bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, + sp<GraphicBuffer> graphicBuffer) override { + ATRACE_CALL(); + + 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)graphicBuffer->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; + } + + { + ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height()); + EGLSyncKHR fence = mUploadThread->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 true; + } + + renderthread::EglManager mEglManager; +}; + +class VkUploader : public AHBUploader { +private: + void onInitialize() override { + std::lock_guard _lock{mLock}; + if (!mUploadThread) { + mUploadThread = new ThreadBase{}; + } + if (!mUploadThread->isRunning()) { + mUploadThread->start("GrallocUploadThread"); + } + + mUploadThread->queue().post([this]() { + std::lock_guard _lock{mVkLock}; + if (!mVulkanManager.hasVkContext()) { + mVulkanManager.initialize(); + } + }); + } + void onDestroy() override { + mGrContext.reset(); + mVulkanManager.destroy(); + } + void onIdle() override { + mGrContext.reset(); + } + + void onBeginUpload() override { + { + std::lock_guard _lock{mVkLock}; + if (!mVulkanManager.hasVkContext()) { + LOG_ALWAYS_FATAL_IF(mGrContext, + "GrContext exists with no VulkanManager for vulkan uploads"); + mUploadThread->queue().runSync([this]() { + mVulkanManager.initialize(); + }); + } + } + if (!mGrContext) { + GrContextOptions options; + mGrContext = mVulkanManager.createContext(options); + LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads"); + this->postIdleTimeoutCheck(); + } + } + + bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, + sp<GraphicBuffer> graphicBuffer) override { + ATRACE_CALL(); + + std::lock_guard _lock{mLock}; + + sk_sp<SkImage> image = SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), + bitmap.pixmap(), reinterpret_cast<AHardwareBuffer*>(graphicBuffer.get())); + return (image.get() != nullptr); + } + + sk_sp<GrContext> mGrContext; + renderthread::VulkanManager mVulkanManager; + std::mutex mVkLock; +}; + +bool HardwareBitmapUploader::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; +} + +static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { + FormatInfo formatInfo; + 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; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + break; + case kRGBA_F16_SkColorType: + formatInfo.isSupported = HardwareBitmapUploader::hasFP16Support(); + if (formatInfo.isSupported) { + formatInfo.type = GL_HALF_FLOAT; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16; + formatInfo.vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + } + 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; + formatInfo.vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16; + break; + case kGray_8_SkColorType: + formatInfo.isSupported = usingGL; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.format = GL_LUMINANCE; + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + 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(info.makeColorType(kN32_SkColorType)); + + SkCanvas canvas(bitmap); + canvas.drawColor(0); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + + return bitmap; + } +} + + +static void createUploader(bool usingGL) { + static std::mutex lock; + std::lock_guard _lock{lock}; + if (!sUploader.get()) { + if (usingGL) { + sUploader = new EGLUploader(); + } else { + sUploader = new VkUploader(); + } + } +} + +sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) { + ATRACE_CALL(); + + bool usingGL = uirenderer::Properties::getRenderPipelineType() == + uirenderer::RenderPipelineType::SkiaGL; + + FormatInfo format = determineFormat(sourceBitmap, usingGL); + if (!format.valid) { + return nullptr; + } + + 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::allocateHardwareBitmap pid [") + std::to_string(getpid()) + + "]"); + + status_t error = buffer->initCheck(); + if (error < 0) { + ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); + return nullptr; + } + + createUploader(usingGL); + + if (!sUploader->uploadHardwareBitmap(bitmap, format, buffer)) { + return nullptr; + } + return Bitmap::createFrom(buffer, bitmap.colorType(), bitmap.refColorSpace(), + bitmap.alphaType(), Bitmap::computePalette(bitmap)); +} + +void HardwareBitmapUploader::initialize() { + bool usingGL = uirenderer::Properties::getRenderPipelineType() == + uirenderer::RenderPipelineType::SkiaGL; + createUploader(usingGL); + sUploader->initialize(); +} + +void HardwareBitmapUploader::terminate() { + if (sUploader) { + sUploader->destroy(); + } +} + +} // namespace android::uirenderer diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h new file mode 100644 index 000000000000..c300593d47a1 --- /dev/null +++ b/libs/hwui/HardwareBitmapUploader.h @@ -0,0 +1,33 @@ +/* + * 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> + +namespace android::uirenderer { + +class ANDROID_API HardwareBitmapUploader { +public: + static void initialize(); + static void terminate(); + + static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap); + + static bool hasFP16Support(); +}; + +} // 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 2016b5e914cd..53c5ad8eff3c 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> @@ -179,6 +180,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..c174c240ff22 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -17,64 +17,40 @@ #include "Layer.h" #include "renderstate/RenderState.h" - -#include <SkToSRGBColorFilter.h> +#include "utils/Color.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() { mRenderState.unregisterLayer(this); } -void Layer::setColorFilter(sk_sp<SkColorFilter> filter) { - if (filter != mColorFilter) { - mColorFilter = filter; - buildColorSpaceWithFilter(); - } -} - -void Layer::setDataSpace(android_dataspace dataspace) { - if (dataspace != mCurrentDataspace) { - mCurrentDataspace = dataspace; - buildColorSpaceWithFilter(); - } +void Layer::postDecStrong() { + mRenderState.postDecStrong(this); } -void Layer::buildColorSpaceWithFilter() { - sk_sp<SkColorFilter> colorSpaceFilter; - sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(mCurrentDataspace); - if (colorSpace && !colorSpace->isSRGB()) { - colorSpaceFilter = SkToSRGBColorFilter::Make(colorSpace); - } - - if (mColorFilter && colorSpaceFilter) { - mColorSpaceWithFilter = mColorFilter->makeComposed(colorSpaceFilter); - } else if (colorSpaceFilter) { - mColorSpaceWithFilter = colorSpaceFilter; +SkBlendMode Layer::getMode() const { + if (mBlend || mode != SkBlendMode::kSrcOver) { + return mode; } else { - mColorSpaceWithFilter = mColorFilter; + return SkBlendMode::kSrc; } } -void Layer::postDecStrong() { - mRenderState.postDecStrong(this); -} - -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index d41c9703e908..ea3bfc9e80cb 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,21 +67,15 @@ public: inline int getAlpha() const { return alpha; } - virtual SkBlendMode getMode() const { return mode; } - - inline SkColorFilter* getColorFilter() const { return mColorFilter.get(); } + SkBlendMode getMode() const; - void setColorFilter(sk_sp<SkColorFilter> filter); + inline sk_sp<SkColorFilter> getColorFilter() const { return mColorFilter; } - void setDataSpace(android_dataspace dataspace); + void setColorFilter(sk_sp<SkColorFilter> filter) { mColorFilter = filter; }; - void setColorSpace(sk_sp<SkColorSpace> colorSpace); + inline SkMatrix& getTexTransform() { return texTransform; } - inline sk_sp<SkColorFilter> getColorSpaceWithFilter() const { return mColorSpaceWithFilter; } - - inline mat4& getTexTransform() { return texTransform; } - - inline mat4& getTransform() { return transform; } + inline SkMatrix& getTransform() { return transform; } /** * Posts a decStrong call to the appropriate thread. @@ -94,70 +83,66 @@ 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; +private: /** - * Blending mode of the layer. + * Color filter used to draw this layer. Optional. */ - SkBlendMode mode; - -private: - void buildColorSpaceWithFilter(); + sk_sp<SkColorFilter> mColorFilter; - Api mApi; + /** + * Indicates raster data backing the layer is scaled, requiring filtration. + */ + bool forceFilter = false; /** - * Color filter used to draw this layer. Optional. + * Opacity of the layer. */ - sk_sp<SkColorFilter> mColorFilter; + int alpha; /** - * Colorspace of the contents of the layer. Optional. + * Blending mode of the layer. */ - android_dataspace mCurrentDataspace = HAL_DATASPACE_UNKNOWN; + SkBlendMode mode; /** - * A color filter that is the combination of the mColorFilter and mColorSpace. Optional. + * Optional texture coordinates transform. */ - sk_sp<SkColorFilter> mColorSpaceWithFilter; + SkMatrix texTransform; /** - * Indicates raster data backing the layer is scaled, requiring filtration. + * Optional transform. */ - bool forceFilter = false; + SkMatrix transform; /** - * Opacity of the layer. + * An image backing the layer. */ - int alpha; + sk_sp<SkImage> layerImage; /** - * Optional texture coordinates transform. + * layer width. */ - mat4 texTransform; + uint32_t mWidth = 0; /** - * Optional transform. + * layer height. */ - mat4 transform; + uint32_t mHeight = 0; - uint32_t mBufferWidth = 0; + /** + * enable blending + */ + bool mBlend = false; - uint32_t mBufferHeight = 0; }; // struct Layer -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android 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..2c63af6aaab4 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> @@ -49,7 +50,7 @@ private: std::vector<Entry> mEntries; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H diff --git a/libs/hwui/protos/ProtoHelpers.h b/libs/hwui/Lighting.h index 833c77f2b8cb..ccfbb93b00ef 100644 --- a/libs/hwui/protos/ProtoHelpers.h +++ b/libs/hwui/Lighting.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,29 +13,26 @@ * 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" +#pragma once + +#include "Vector.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); -} +struct LightGeometry { + Vector3 center; + float radius; +}; -void set(std::string* dest, const SkPath& src) { - size_t size = src.writeToMemory(nullptr); - dest->resize(size); - src.writeToMemory(&*dest->begin()); -} +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 } // namespace android - -#endif // PROTOHELPERS_H diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp index d84ed321a4cb..d0dbff031e99 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -526,5 +526,5 @@ void Matrix4::dump(const char* label) const { ALOGD("]"); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 9f3c0213c5b5..0c515a41689d 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -27,6 +27,7 @@ namespace android { namespace uirenderer { #define SK_MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]" +#define SK_MATRIX_STRING_V "[%.9f %.9f %.9f] [%.9f %.9f %.9f] [%.9f %.9f %.9f]" #define SK_MATRIX_ARGS(m) \ (m)->get(0), (m)->get(1), (m)->get(2), (m)->get(3), (m)->get(4), (m)->get(5), (m)->get(6), \ (m)->get(7), (m)->get(8) @@ -244,5 +245,5 @@ private: typedef Matrix4 mat4; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/NinePatchUtils.h b/libs/hwui/NinePatchUtils.h index db9509fff378..86d3cb9a5b11 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)); @@ -104,5 +103,5 @@ static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, } } -}; // namespace NinePatchUtils -}; // namespace android +} // namespace NinePatchUtils +} // namespace android diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS new file mode 100644 index 000000000000..936ba5cc8311 --- /dev/null +++ b/libs/hwui/OWNERS @@ -0,0 +1,6 @@ +jreck@google.com +njawad@google.com +djsollen@google.com +stani@google.com +scroggo@google.com +reed@google.com 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..808921d344da 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]); @@ -305,5 +304,5 @@ void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, return; } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h index 474eb97b53c6..f5bebce605fb 100644 --- a/libs/hwui/PathParser.h +++ b/libs/hwui/PathParser.h @@ -46,6 +46,6 @@ public: static void validateVerbAndPoints(char verb, size_t points, ParseResult* result); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_PATHPARSER_H 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..af20c4f4f20e 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -17,6 +17,8 @@ #include "Properties.h" #include "Debug.h" #include "DeviceInfo.h" +#include "HWUIProperties.sysprop.h" +#include "SkTraceEventCommon.h" #include <algorithm> #include <cstdlib> @@ -28,8 +30,6 @@ namespace android { namespace uirenderer { -bool Properties::drawDeferDisabled = false; -bool Properties::drawReorderDisabled = false; bool Properties::debugLayersUpdates = false; bool Properties::debugOverdraw = false; bool Properties::showDirtyRegions = false; @@ -39,7 +39,6 @@ bool Properties::enablePartialUpdates = true; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; -StencilClipDebug Properties::debugStencilClip = StencilClipDebug::Hide; float Properties::overrideLightRadius = -1.0f; float Properties::overrideLightPosY = -1.0f; @@ -66,6 +65,7 @@ bool Properties::debuggingEnabled = false; bool Properties::isolatedProcess = false; int Properties::contextPriority = 0; +int Properties::defaultRenderAhead = -1; static int property_get_int(const char* key, int defaultValue) { char buf[PROPERTY_VALUE_MAX] = { @@ -82,7 +82,6 @@ bool Properties::load() { char property[PROPERTY_VALUE_MAX]; bool prevDebugLayersUpdates = debugLayersUpdates; bool prevDebugOverdraw = debugOverdraw; - StencilClipDebug prevDebugStencilClip = debugStencilClip; debugOverdraw = false; if (property_get(PROPERTY_DEBUG_OVERDRAW, property, nullptr) > 0) { @@ -96,20 +95,6 @@ bool Properties::load() { } } - // See Properties.h for valid values - if (property_get(PROPERTY_DEBUG_STENCIL_CLIP, property, nullptr) > 0) { - INIT_LOGD(" Stencil clip debug enabled: %s", property); - if (!strcmp(property, "hide")) { - debugStencilClip = StencilClipDebug::Hide; - } else if (!strcmp(property, "highlight")) { - debugStencilClip = StencilClipDebug::ShowHighlight; - } else if (!strcmp(property, "region")) { - debugStencilClip = StencilClipDebug::ShowRegion; - } - } else { - debugStencilClip = StencilClipDebug::Hide; - } - sProfileType = ProfileType::None; if (property_get(PROPERTY_PROFILE, property, "") > 0) { if (!strcmp(property, PROPERTY_PROFILE_VISUALIZE_BARS)) { @@ -122,12 +107,6 @@ bool Properties::load() { debugLayersUpdates = property_get_bool(PROPERTY_DEBUG_LAYERS_UPDATES, false); INIT_LOGD(" Layers updates debug enabled: %d", debugLayersUpdates); - drawDeferDisabled = property_get_bool(PROPERTY_DISABLE_DRAW_DEFER, false); - INIT_LOGD(" Draw defer %s", drawDeferDisabled ? "disabled" : "enabled"); - - drawReorderDisabled = property_get_bool(PROPERTY_DISABLE_DRAW_REORDER, false); - INIT_LOGD(" Draw reorder %s", drawReorderDisabled ? "disabled" : "enabled"); - showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false); debugLevel = (DebugLevel)property_get_int(PROPERTY_DEBUG, kDebugDisabled); @@ -140,10 +119,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); - return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) || - (prevDebugStencilClip != debugStencilClip); + defaultRenderAhead = std::max(-1, std::min(2, property_get_int(PROPERTY_RENDERAHEAD, + render_ahead().value_or(0)))); + + return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } void Properties::overrideProperty(const char* name, const char* value) { @@ -185,22 +169,22 @@ ProfileType Properties::getProfileType() { return sProfileType; } -RenderPipelineType Properties::getRenderPipelineType() { +RenderPipelineType Properties::peekRenderPipelineType() { + // If sRenderPipelineType has been locked, just return the locked type immediately. if (sRenderPipelineType != RenderPipelineType::NotInitialized) { return sRenderPipelineType; } + bool useVulkan = use_vulkan().value_or(false); 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")) { - ALOGD("Skia Vulkan Pipeline"); - sRenderPipelineType = RenderPipelineType::SkiaVulkan; - } else { //"opengl" - ALOGD("HWUI GL Pipeline"); - sRenderPipelineType = RenderPipelineType::OpenGL; + property_get(PROPERTY_RENDERER, prop, useVulkan ? "skiavk" : "skiagl"); + if (!strcmp(prop, "skiavk")) { + return RenderPipelineType::SkiaVulkan; } + return RenderPipelineType::SkiaGL; +} + +RenderPipelineType Properties::getRenderPipelineType() { + sRenderPipelineType = peekRenderPipelineType(); return sRenderPipelineType; } @@ -216,10 +200,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 +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 764c50259540..71b07c947716 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -95,20 +95,6 @@ enum DebugLevel { #define PROPERTY_PROFILE_VISUALIZE_BARS "visual_bars" /** - * Used to enable/disable non-rectangular clipping debugging. - * - * The accepted values are: - * "highlight", drawing commands clipped by the stencil will - * be colored differently - * "region", renders the clipping region on screen whenever - * the stencil is set - * "hide", don't show the clip - * - * The default value is "hide". - */ -#define PROPERTY_DEBUG_STENCIL_CLIP "debug.hwui.show_non_rect_clip" - -/** * Turn on to draw dirty regions every other frame. * * Possible values: @@ -118,19 +104,6 @@ enum DebugLevel { #define PROPERTY_DEBUG_SHOW_DIRTY_REGIONS "debug.hwui.show_dirty_regions" /** - * Disables draw operation deferral if set to "true", forcing draw - * commands to be issued to OpenGL in order, and processed in sequence - * with state-manipulation canvas commands. - */ -#define PROPERTY_DISABLE_DRAW_DEFER "debug.hwui.disable_draw_defer" - -/** - * Used to disable draw operation reordering when deferring draw operations - * Has no effect if PROPERTY_DISABLE_DRAW_DEFER is set to "true" - */ -#define PROPERTY_DISABLE_DRAW_REORDER "debug.hwui.disable_draw_reorder" - -/** * Setting this property will enable or disable the dropping of frames with * empty damage. Default is "true". */ @@ -171,6 +144,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 +163,8 @@ enum DebugLevel { */ #define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" +#define PROPERTY_RENDERAHEAD "debug.hwui.render_ahead" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -198,9 +178,7 @@ enum class ProfileType { None, Console, Bars }; 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 @@ -211,8 +189,6 @@ class Properties { public: static bool load(); - static bool drawDeferDisabled; - static bool drawReorderDisabled; static bool debugLayersUpdates; static bool debugOverdraw; static bool showDirtyRegions; @@ -226,7 +202,6 @@ public: static DebugLevel debugLevel; static OverdrawColorSet overdrawColorSet; - static StencilClipDebug debugStencilClip; // Override the value for a subset of properties in this class static void overrideProperty(const char* name, const char* value); @@ -239,8 +214,8 @@ public: static int overrideSpotShadowStrength; static ProfileType getProfileType(); + ANDROID_API static RenderPipelineType peekRenderPipelineType(); ANDROID_API static RenderPipelineType getRenderPipelineType(); - static bool isSkiaEnabled(); ANDROID_API static bool enableHighContrastText; @@ -273,13 +248,15 @@ public: ANDROID_API static int contextPriority; + static int defaultRenderAhead; + private: static ProfileType sProfileType; static bool sDisableProfileBars; static RenderPipelineType sRenderPipelineType; }; // class Caches -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_PROPERTIES_H diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp new file mode 100644 index 000000000000..89a9b997af97 --- /dev/null +++ b/libs/hwui/Readback.cpp @@ -0,0 +1,211 @@ +/* + * 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 <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" +#include "utils/TraceUtils.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<SkImage> image = SkImage::MakeFromAHardwareBuffer( + reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()), + kPremul_SkAlphaType, colorSpace); + return copyImageInto(image, 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); + + return copyImageInto(hwBitmap->makeImage(), transform, srcRect, bitmap); +} + +CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) { + ATRACE_CALL(); + 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, Matrix4& texTransform, + const Rect& srcRect, SkBitmap* bitmap) { + ATRACE_CALL(); + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + mRenderThread.requireGlContext(); + } else { + mRenderThread.requireVkContext(); + } + 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; + } + + Layer layer(mRenderThread.renderState(), nullptr, 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) { + /* 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, bitmap->info(), 0, + kTopLeft_GrSurfaceOrigin, nullptr); + + // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we + // attempt to do the intermediate rendering step in 8888 + if (!tmpSurface.get()) { + SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); + tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); + if (!tmpSurface.get()) { + ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); + return false; + } + } + + if (!skiapipeline::LayerDrawable::DrawLayer(mRenderThread.getGrContext(), + tmpSurface->getCanvas(), layer, srcRect, dstRect, + false)) { + ALOGW("Unable to draw content from GPU into the provided bitmap"); + return false; + } + + if (!tmpSurface->readPixels(*bitmap, 0, 0)) { + // 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. + SkBitmap tmpBitmap; + SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType); + if (bitmap->info().colorType() == SkColorType::kN32_SkColorType || + !tmpBitmap.tryAllocPixels(tmpInfo) || + !tmpSurface->readPixels(tmpBitmap, 0, 0) || + !tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), + bitmap->rowBytes(), 0, 0)) { + ALOGW("Unable to convert content into the provided bitmap"); + return false; + } + } + + bitmap->notifyPixelsChanged(); + return true; +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index ad3a8b690617..e86a8136cfa3 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, 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..e58fbbe8e667 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,616 +16,970 @@ #include "RecordingCanvas.h" -#include "DeferredLayerUpdater.h" -#include "RecordedOp.h" -#include "RenderNode.h" #include "VectorDrawable.h" -#include "hwui/MinikinUtils.h" + +#include "SkAndroidFrameworkUtils.h" +#include "SkCanvas.h" +#include "SkCanvasPriv.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); -} - -RecordingCanvas::~RecordingCanvas() { - LOG_ALWAYS_FATAL_IF(mDisplayList, "Destroyed a RecordingCanvas during a record!"); -} - -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); - - mDeferredBarrierType = DeferredBarrierType::InOrder; -} - -DisplayList* RecordingCanvas::finishRecording() { - restoreToCount(1); - mPaintMap.clear(); - mRegionMap.clear(); - mPathMap.clear(); - DisplayList* displayList = mDisplayList; - mDisplayList = nullptr; - mSkiaCanvasProxy.reset(nullptr); - return displayList; -} - -void RecordingCanvas::insertReorderBarrier(bool enableReorder) { - if (enableReorder) { - mDeferredBarrierType = DeferredBarrierType::OutOfOrder; - mDeferredBarrierClip = getRecordedClip(); - } else { - mDeferredBarrierType = DeferredBarrierType::InOrder; - mDeferredBarrierClip = nullptr; +#ifndef SKLITEDL_PAGE +#define SKLITEDL_PAGE 4096 +#endif + +// 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; +} + +// copy_v(dst, src,n, src,n, ...) copies an arbitrary number of typed srcs into dst. +static void copy_v(void* dst) {} + +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)...); +} + +// 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); +} + +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 SaveBehind final : Op { + static const auto kType = Type::SaveBehind; + SaveBehind(const SkRect* subset) { + if (subset) { this->subset = *subset; } + } + SkRect subset = kUnset; + void draw(SkCanvas* c, const SkMatrix&) const { + SkAndroidFrameworkUtils::SaveBehind(c, &subset); + } +}; + +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 DrawBehind final : Op { + static const auto kType = Type::DrawBehind; + DrawBehind(const SkPaint& paint) : paint(paint) {} + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { SkCanvasPriv::DrawBehind(c, 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 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; +} + +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; } } -// ---------------------------------------------------------------------------- -// android/graphics/Canvas state operations -// ---------------------------------------------------------------------------- -// Save (layer) -int RecordingCanvas::save(SaveFlags::Flags flags) { - return mState.save((int)flags); +void DisplayListData::flush() { + this->push<Flush>(0); } -void RecordingCanvas::RecordingCanvas::restore() { - mState.restore(); +void DisplayListData::save() { + this->push<Save>(0); } - -void RecordingCanvas::restoreToCount(int saveCount) { - mState.restoreToCount(saveCount); +void DisplayListData::restore() { + this->push<Restore>(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::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); } -// Matrix -void RecordingCanvas::rotate(float degrees) { - if (degrees == 0) return; - - mState.rotate(degrees); +void DisplayListData::saveBehind(const SkRect* subset) { + this->push<SaveBehind>(0, subset); } -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::drawBehind(const SkPaint& paint) { + this->push<DrawBehind>(0, 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::drawPath(const SkPath& path, const SkPaint& paint) { + this->push<DrawPath>(0, path, paint); } -bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkClipOp op) { - return mState.clipRect(left, top, right, bottom, op); +void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) { + this->push<DrawRect>(0, rect, paint); } -bool RecordingCanvas::clipPath(const SkPath* path, SkClipOp op) { - return mState.clipPath(path, op); +void DisplayListData::drawRegion(const SkRegion& region, const SkPaint& paint) { + this->push<DrawRegion>(0, region, paint); +} +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::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); - addOp(alloc().create_trivial<PointsOp>( - calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, - getRecordedClip(), refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); -} +// 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 -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 +// 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, - addOp(alloc().create_trivial<LinesOp>( - calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, - getRecordedClip(), refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); -} +static const void_fn dtor_fns[] = { +#include "DisplayListOps.in" +}; +#undef X -void RecordingCanvas::drawRect(float left, float top, float right, float bottom, - const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; +void DisplayListData::draw(SkCanvas* canvas) const { + SkAutoCanvasRestore acr(canvas, false); + this->map(draw_fns, canvas, canvas->getTotalMatrix()); +} - addOp(alloc().create_trivial<RectOp>(Rect(left, top, right, bottom), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(&paint))); +DisplayListData::~DisplayListData() { + this->reset(); } -void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { - if (rects == nullptr) return; +void DisplayListData::reset() { + this->map(dtor_fns); - Vertex* rectData = (Vertex*)mDisplayList->allocator.create_trivial_array<Vertex>(vertexCount); - Vertex* vertex = rectData; + // Leave fBytes and fReserved alone. + fUsed = 0; +} - 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]; +template <class T> +using has_paint_helper = decltype(std::declval<T>().paint); - Vertex::set(vertex++, l, t); - Vertex::set(vertex++, r, t); - Vertex::set(vertex++, l, b); - Vertex::set(vertex++, r, b); +template <class T> +constexpr bool has_paint = std::experimental::is_detected_v<has_paint_helper, T>; - 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)); -} +template <class T> +using has_palette_helper = decltype(std::declval<T>().palette); -void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; +template <class T> +constexpr bool has_palette = std::experimental::is_detected_v<has_palette_helper, T>; - 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> +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; - - 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); - } -} +#define X(T) colorTransformForOp<T>(), +static const color_transform_fn color_transform_fns[] = { +#include "DisplayListOps.in" +}; +#undef X -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; + mClipMayBeComplex = false; + mSaveCount = mComplexSaveCount = 0; } -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() { + mSaveCount++; + 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() { + mSaveCount--; + if (mSaveCount < mComplexSaveCount) { + mClipMayBeComplex = false; + mComplexSaveCount = 0; } + fDL->restore(); } -void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; +bool RecordingCanvas::onDoSaveBehind(const SkRect* subset) { + fDL->saveBehind(subset); + return false; +} - 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); + if (!getTotalMatrix().isScaleTranslate()) { + setClipMayBeComplex(); + } + this->INHERITED::onClipRect(rect, op, style); +} +void RecordingCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle style) { + if (rrect.getType() > SkRRect::kRect_Type || !getTotalMatrix().isScaleTranslate()) { + setClipMayBeComplex(); } + fDL->clipRRect(rrect, op, style == kSoft_ClipEdgeStyle); + this->INHERITED::onClipRRect(rrect, op, style); +} +void RecordingCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle style) { + setClipMayBeComplex(); + fDL->clipPath(path, op, style == kSoft_ClipEdgeStyle); + this->INHERITED::onClipPath(path, op, style); +} +void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) { + if (region.isComplex() || !getTotalMatrix().isScaleTranslate()) { + setClipMayBeComplex(); + } + 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::onDrawBehind(const SkPaint& paint) { + fDL->drawBehind(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::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 -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index e663402a80f3..7269bcad3d7a 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,233 @@ * 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; - -class ANDROID_API RecordingCanvas : public Canvas, public CanvasStateClient { - enum class DeferredBarrierType { - None, - InOrder, - OutOfOrder, - }; - -public: - RecordingCanvas(size_t width, size_t height); - virtual ~RecordingCanvas(); - - virtual void resetRecording(int width, int height, 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."); - } +enum class DisplayListOpType : uint8_t { +#define X(T) T, +#include "DisplayListOps.in" +#undef X +}; - 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); - } +struct DisplayListOp { + const uint8_t type : 8; + const uint32_t skip : 24; +}; - // Matrix - virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); } - virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); } +static_assert(sizeof(DisplayListOp) == 4); - 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; +class RecordingCanvas; - // 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; +class DisplayListData final { +public: + DisplayListData() : mHasText(false) {} + ~DisplayListData(); - virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override; - virtual bool clipPath(const SkPath* path, SkClipOp op) override; + void draw(SkCanvas* canvas) const; - // Misc - virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); } - virtual void setDrawFilter(SkDrawFilter* filter) override { - mDrawFilter.reset(SkSafeRef(filter)); - } + void reset(); + bool empty() const { return fUsed == 0; } - // ---------------------------------------------------------------------------- - // android/graphics/Canvas draw operations - // ---------------------------------------------------------------------------- - virtual void drawColor(int color, SkBlendMode mode) override; - virtual void drawPaint(const SkPaint& paint) override; + void applyColorTransform(ColorTransform transform); - // 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; + bool hasText() const { return mHasText; } + size_t usedSize() const { return fUsed; } 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; - } + friend class RecordingCanvas; + + void flush(); + + void save(); + void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, const SkImage*, + const SkMatrix*, SkCanvas::SaveLayerFlags); + void saveBehind(const SkRect*); + 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 drawBehind(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 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(); + 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; + bool onDoSaveBehind(const SkRect*) 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 onDrawBehind(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 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); /** - * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in - * (with deduping based on paint hash / equality check) + * If "isClipMayBeComplex" returns false, it is guaranteed the current clip is a rectangle. + * If the return value is true, then clip may or may not be complex (there is no guarantee). */ - 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()); - } + inline bool isClipMayBeComplex() { return mClipMayBeComplex; } - 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)); +private: + typedef SkCanvasVirtualEnforcer<SkNoDrawCanvas> INHERITED; - // replaceValueFor() performs an add if the entry doesn't exist - mRegionMap.replaceValueFor(region, cachedRegion); + inline void setClipMayBeComplex() { + if (!mClipMayBeComplex) { + mComplexSaveCount = mSaveCount; + mClipMayBeComplex = true; } - - 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; - } + DisplayListData* fDL; - 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; + /** + * mClipMayBeComplex tracks if the current clip is a rectangle. This flag is used to promote + * FunctorDrawable to a layer, if it is clipped by a non-rect. + */ + bool mClipMayBeComplex = false; - 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 + /** + * mSaveCount is the current level of our save tree. + */ + int mSaveCount = 0; -}; // namespace uirenderer -}; // namespace android + /** + * mComplexSaveCount is the first save level, which has a complex clip. Every level below + * mComplexSaveCount is assumed to have a complex clip and every level above mComplexSaveCount + * is guaranteed to not be complex. + */ + int mComplexSaveCount = 0; +}; -#endif // ANDROID_HWUI_RECORDING_CANVAS_H +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 320190fec448..24443c8c9836 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -262,5 +262,5 @@ public: } }; // class Rect -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android 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..b73347b233d7 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,11 +27,9 @@ #include "utils/StringUtils.h" #include "utils/TraceUtils.h" -#include "protos/ProtoHelpers.h" -#include "protos/hwui.pb.h" - #include <SkPathOps.h> #include <algorithm> +#include <atomic> #include <sstream> #include <string> @@ -52,8 +48,14 @@ private: TreeInfo* mTreeInfo; }; +static int64_t generateId() { + static std::atomic<int64_t> sNextId{1}; + return sNextId++; +} + RenderNode::RenderNode() - : mDirtyPropertyFields(0) + : mUniqueId(generateId()) + , mDirtyPropertyFields(0) , mNeedsDisplayListSync(false) , mDisplayList(nullptr) , mStagingDisplayList(nullptr) @@ -101,77 +103,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 +119,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) { @@ -205,6 +134,7 @@ void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) { void RenderNode::damageSelf(TreeInfo& info) { if (isRenderable()) { + mDamageGenerationId = info.damageGenerationId; if (properties().getClipDamageToBounds()) { info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight()); } else { @@ -238,7 +168,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; } @@ -270,11 +200,22 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer. */ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) { + if (mDamageGenerationId == info.damageGenerationId) { + // We hit the same node a second time in the same tree. We don't know the minimal + // damage rect anymore, so just push the biggest we can onto our parent's transform + // We push directly onto parent in case we are clipped to bounds but have moved position. + info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } info.damageAccumulator->pushTransform(this); 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 +253,9 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu } pushLayerUpdate(info); + if (!mProperties.getAllowForceDark()) { + info.disableForceDark--; + } info.damageAccumulator->popTransform(); } @@ -320,6 +264,12 @@ void RenderNode::syncProperties() { } void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { + if (mPositionListenerDirty) { + mPositionListener = std::move(mStagingPositionListener); + mStagingPositionListener = nullptr; + mPositionListenerDirty = false; + } + // Push the animators first so that setupStartValueIfNecessary() is called // before properties() is trampled by stagingProperties(), as they are // required by some animators. @@ -351,8 +301,49 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { mDisplayList = mStagingDisplayList; mStagingDisplayList = nullptr; if (mDisplayList) { - mDisplayList->syncContents(); + WebViewSyncData syncData { + .applyForceDark = info && !info->disableForceDark + }; + mDisplayList->syncContents(syncData); + 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 +370,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 +380,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 +450,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(); @@ -548,5 +467,42 @@ const SkPath* RenderNode::getClippedOutline(const SkRect& clipRect) const { return &mClippedOutlineCache.clippedOutline; } +using StringBuffer = FatVector<char, 128>; + +template <typename... T> +// TODO:__printflike(2, 3) +// Doesn't work because the warning doesn't understand string_view and doesn't like that +// it's not a C-style variadic function. +static void format(StringBuffer& buffer, const std::string_view& format, T... args) { + buffer.resize(buffer.capacity()); + while (1) { + int needed = snprintf(buffer.data(), buffer.size(), + format.data(), std::forward<T>(args)...); + if (needed < 0) { + buffer[0] = '\0'; + buffer.resize(1); + return; + } + if (needed < buffer.size()) { + buffer.resize(needed + 1); + return; + } + // If we're doing a heap alloc anyway might as well give it some slop + buffer.resize(needed + 100); + } +} + +void RenderNode::markDrawStart(SkCanvas& canvas) { + StringBuffer buffer; + format(buffer, "RenderNode(id=%" PRId64 ", name='%s')", uniqueId(), getName()); + canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr); +} + +void RenderNode::markDrawEnd(SkCanvas& canvas) { + StringBuffer buffer; + format(buffer, "/RenderNode(id=%" PRId64 ", name='%s')", uniqueId(), getName()); + canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 1469a156e2d8..c6db7f1ba60d 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. @@ -197,11 +188,9 @@ public: virtual void onPositionLost(RenderNode& node, const TreeInfo* info) = 0; }; - // Note this is not thread safe, this needs to be called - // before the RenderNode is used for drawing. - // RenderNode takes ownership of the pointer ANDROID_API void setPositionListener(PositionListener* listener) { - mPositionListener = listener; + mStagingPositionListener = listener; + mPositionListenerDirty = true; } // This is only modified in MODE_FULL, so it can be safely accessed @@ -218,6 +207,15 @@ public: void output(std::ostream& output, uint32_t level); + void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; } + + UsageHint usageHint() const { return mUsageHint; } + + int64_t uniqueId() const { return mUniqueId; } + + void markDrawStart(SkCanvas& canvas); + void markDrawEnd(SkCanvas& canvas); + private: void computeOrderingImpl(RenderNodeOp* opState, std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface, @@ -225,6 +223,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); @@ -237,6 +236,7 @@ private: void incParentRefCount() { mParentCount++; } void decParentRefCount(TreeObserver& observer, TreeInfo* info = nullptr); + const int64_t mUniqueId; String8 mName; sp<VirtualLightRefBase> mUserContext; @@ -253,13 +253,11 @@ private: DisplayList* mDisplayList; DisplayList* mStagingDisplayList; + int64_t mDamageGenerationId; + 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 */ @@ -275,8 +273,12 @@ private: // mDisplayList, not mStagingDisplayList. uint32_t mParentCount; + bool mPositionListenerDirty = false; + sp<PositionListener> mStagingPositionListener; sp<PositionListener> mPositionListener; + UsageHint mUsageHint = UsageHint::Unknown; + // METHODS & FIELDS ONLY USED BY THE SKIA RENDERER public: /** @@ -298,7 +300,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..e6710cc8f950 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 @@ -101,13 +98,14 @@ public: LayerProperties& operator=(const LayerProperties& other); + // Strongly recommend using effectiveLayerType instead + LayerType type() const { return mType; } + private: LayerProperties(); ~LayerProperties(); void reset(); - - // Private since external users should go through properties().effectiveLayerType() - LayerType type() const { return mType; } + bool setColorFilter(SkColorFilter* filter); friend class RenderProperties; @@ -116,7 +114,7 @@ private: bool mOpaque; uint8_t mAlpha; SkBlendMode mMode; - SkColorFilter* mColorFilter = nullptr; + sk_sp<SkColorFilter> mColorFilter; }; /* @@ -153,6 +151,7 @@ public: // parent may have already dictated that a descendant layer is needed bool functorsNeedLayer = ancestorDictatesFunctorsNeedLayer + || CC_UNLIKELY(isClipMayBeComplex()) // Round rect clipping forces layer for functors || CC_UNLIKELY(getOutline().willRoundRectClip()) || @@ -195,6 +194,12 @@ public: bool isProjectionReceiver() const { return mPrimitiveFields.mProjectionReceiver; } + bool setClipMayBeComplex(bool isClipMayBeComplex) { + return RP_SET(mPrimitiveFields.mClipMayBeComplex, isClipMayBeComplex); + } + + bool isClipMayBeComplex() const { return mPrimitiveFields.mClipMayBeComplex; } + bool setStaticMatrix(const SkMatrix* matrix) { delete mStaticMatrix; if (matrix) { @@ -328,9 +333,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 +513,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 +542,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 +569,8 @@ private: bool mMatrixOrPivotDirty = false; bool mProjectBackwards = false; bool mProjectionReceiver = false; + bool mAllowForceDark = true; + bool mClipMayBeComplex = false; 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..bebda8527def 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -21,15 +21,17 @@ #include "VectorDrawable.h" #include "hwui/Bitmap.h" #include "hwui/MinikinUtils.h" +#include "hwui/PaintFilter.h" #include "pipeline/skia/AnimatedDrawables.h" +#include <SkAndroidFrameworkUtils.h> #include <SkAnimatedImage.h> +#include <SkCanvasPriv.h> #include <SkCanvasStateUtils.h> #include <SkColorFilter.h> -#include <SkColorSpaceXformCanvas.h> #include <SkDeque.h> -#include <SkDrawFilter.h> #include <SkDrawable.h> +#include <SkFont.h> #include <SkGraphics.h> #include <SkImage.h> #include <SkImagePriv.h> @@ -40,6 +42,8 @@ #include <SkTextBlob.h> #include <memory> +#include <optional> +#include <utility> namespace android { @@ -58,25 +62,8 @@ SkiaCanvas::SkiaCanvas() {} SkiaCanvas::SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {} SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) { - sk_sp<SkColorSpace> cs = bitmap.refColorSpace(); - 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(); - } - } 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 - * destination's colorspace. - */ - mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), std::move(cs)); - mCanvas = mCanvasWrapper.get(); - } + mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap)); + mCanvas = mCanvasOwned.get(); } SkiaCanvas::~SkiaCanvas() {} @@ -85,7 +72,6 @@ void SkiaCanvas::reset(SkCanvas* skiaCanvas) { if (mCanvas != skiaCanvas) { mCanvas = skiaCanvas; mCanvasOwned.reset(); - mCanvasWrapper.reset(); } mSaveStack.reset(nullptr); } @@ -95,20 +81,9 @@ void SkiaCanvas::reset(SkCanvas* skiaCanvas) { // ---------------------------------------------------------------------------- void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { - sk_sp<SkColorSpace> cs = bitmap.refColorSpace(); - std::unique_ptr<SkCanvas> newCanvas = - std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy)); - 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) - mCanvasOwned = std::move(newCanvas); - mCanvasWrapper = std::move(newCanvasWrapper); - mCanvas = mCanvasWrapper ? mCanvasWrapper.get() : mCanvasOwned.get(); + mCanvasOwned.reset(new SkCanvas(bitmap)); + mCanvas = mCanvasOwned.get(); // clean up the old save stack mSaveStack.reset(nullptr); @@ -212,6 +187,23 @@ int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom, return this->saveLayer(left, top, right, bottom, nullptr, flags); } +int SkiaCanvas::saveUnclippedLayer(int left, int top, int right, int bottom) { + SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); + return SkAndroidFrameworkUtils::SaveBehind(mCanvas, &bounds); +} + +void SkiaCanvas::restoreUnclippedLayer(int restoreCount, const SkPaint& paint) { + + while (mCanvas->getSaveCount() > restoreCount + 1) { + this->restore(); + } + + if (mCanvas->getSaveCount() == restoreCount + 1) { + SkCanvasPriv::DrawBehind(mCanvas, *filterPaint(paint)); + this->restore(); + } +} + class SkiaCanvas::Clip { public: Clip(const SkRect& rect, SkClipOp op, const SkMatrix& m) @@ -219,7 +211,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 +223,7 @@ public: canvas->clipRRect(mRRect, mOp); break; case Type::Path: - canvas->clipPath(*mPath.get(), mOp); + canvas->clipPath(mPath.value(), mOp); break; } } @@ -248,7 +240,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 +400,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 +439,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 +464,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 +523,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,59 +534,25 @@ 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) { - /* 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 - * should only be used if this canvas is backed by a surface/bitmap that is known - * 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()); - } else { - tmpPaint->setColorFilter(colorSpaceFilter); - } - - return tmpPaint; - } else { - return origPaint; - } -} - 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(bitmap.makeImage(), left, top, filterPaint(paint)); } 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(bitmap.makeImage(), 0, 0, filterPaint(paint)); } void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, @@ -591,10 +561,7 @@ 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(bitmap.makeImage(), srcRect, dstRect, filterPaint(paint), SkCanvas::kFast_SrcRectConstraint); } @@ -673,21 +640,16 @@ 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<SkImage> image = bitmap.makeImage(); sk_sp<SkShader> shader = image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode); - if (colorFilter) { - shader = shader->makeWithColorFilter(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 +676,7 @@ 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(bitmap.makeImage().get(), lattice, dst, filterPaint(paint)); } double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) { @@ -732,28 +691,24 @@ void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { // Canvas draw operations: Text // ---------------------------------------------------------------------------- -void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x, +void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, 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); - SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); + Paint paintCopy(paint); + if (mPaintFilter) { + mPaintFilter->filterFullPaint(&paintCopy); + } + const SkFont& font = paintCopy.getSkFont(); // 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); } - SkRect bounds = - SkRect::MakeLTRB(boundsLeft + x, boundsTop + y, boundsRight + x, boundsBottom + y); - SkTextBlobBuilder builder; - const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(paintCopy, count, &bounds); + const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(font, count); glyphFunc(buffer.glyphs, buffer.pos); sk_sp<SkTextBlob> textBlob(builder.make()); @@ -762,19 +717,19 @@ 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, + const Paint& 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); - SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); + Paint paintCopy(paint); + if (mPaintFilter) { + mPaintFilter->filterFullPaint(&paintCopy); + } + const SkFont& font = paintCopy.getSkFont(); const int N = end - start; - SkAutoSTMalloc<1024, uint8_t> storage(N * (sizeof(uint16_t) + sizeof(SkRSXform))); - SkRSXform* xform = (SkRSXform*)storage.get(); - uint16_t* glyphs = (uint16_t*)(xform + N); + SkTextBlobBuilder builder; + auto rec = builder.allocRunRSXform(font, N); + SkRSXform* xform = (SkRSXform*)rec.pos; + uint16_t* glyphs = rec.glyphs; SkPathMeasure meas(path, false); for (size_t i = start; i < end; i++) { @@ -795,7 +750,7 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y(); } - this->asSkCanvas()->drawTextRSXform(glyphs, sizeof(uint16_t) * N, xform, nullptr, paintCopy); + this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy); } // ---------------------------------------------------------------------------- diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 3efc22a03cdf..bbe91eb2fbc4 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 { @@ -67,11 +69,13 @@ public: virtual int save(SaveFlags::Flags flags) override; virtual void restore() override; virtual void restoreToCount(int saveCount) override; + virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) 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; + virtual int saveUnclippedLayer(int left, int top, int right, int bottom) override; virtual void getMatrix(SkMatrix* outMatrix) const override; virtual void setMatrix(const SkMatrix& matrix) override; @@ -87,8 +91,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 +109,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; @@ -151,13 +159,53 @@ protected: void reset(SkCanvas* skiaCanvas); void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); } - virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x, + virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& 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, + const Paint& 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,17 +222,24 @@ 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; - std::unique_ptr<SkCanvas> mCanvasWrapper; // might own a wrapper on the canvas std::unique_ptr<SkCanvas> mCanvasOwned; // might own a canvas we allocated SkCanvas* mCanvas; // we do NOT own this canvas, it must survive us // 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/TEST_MAPPING b/libs/hwui/TEST_MAPPING new file mode 100644 index 000000000000..d9f2acbb49d2 --- /dev/null +++ b/libs/hwui/TEST_MAPPING @@ -0,0 +1,13 @@ +{ + "presubmit": [ + { + "name": "CtsUiRenderingTestCases" + }, + { + "name": "CtsGraphicsTestCases" + }, + { + "name": "CtsAccelerationTestCases" + } + ] +}
\ No newline at end of file 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..dc53dd6c27c3 --- /dev/null +++ b/libs/hwui/TreeInfo.cpp @@ -0,0 +1,31 @@ +/* + * 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) + , damageGenerationId(canvasContext.getFrameNumber()) + , disableForceDark(canvasContext.useForceDark() ? 0 : 1) + , screenSize(canvasContext.getNextFrameSize()) {} + +} // namespace android::uirenderer diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index f2766d6a5b6e..7e8d12fd4597 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,9 +16,11 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include <utils/Timers.h> +#include "SkSize.h" #include <string> @@ -39,7 +41,7 @@ public: virtual void onError(const std::string& message) = 0; protected: - virtual ~ErrorHandler() {} + virtual ~ErrorHandler() = default; }; class TreeObserver { @@ -51,7 +53,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 +72,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 @@ -87,12 +88,17 @@ public: // Must not be null during actual usage DamageAccumulator* damageAccumulator = nullptr; + int64_t damageGenerationId = 0; LayerUpdateQueue* layerUpdateQueue = nullptr; ErrorHandler* errorHandler = nullptr; bool updateWindowPositions = false; + int disableForceDark; + + const SkISize screenSize; + struct Out { bool hasFunctors = false; // This is only updated if evaluateAnimations is true diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h index b495e3394bc9..833ca4a79ce5 100644 --- a/libs/hwui/UvMapper.h +++ b/libs/hwui/UvMapper.h @@ -124,7 +124,7 @@ private: float mMaxV; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_UV_MAPPER_H diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h index d2c15ad872a5..e6eea1c5c0ca 100644 --- a/libs/hwui/Vector.h +++ b/libs/hwui/Vector.h @@ -113,7 +113,7 @@ public: } }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VECTOR_H diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 18358e25fd5b..5418b337c371 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,34 @@ 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) { + if (canvas->quickReject(bounds)) { + // The RenderNode is on screen, but the AVD is not. + return; + } + + // Update the paint for any animatable properties + SkPaint paint = inPaint; + paint.setAlpha(mProperties.getRootAlpha() * 255); + + if (canvas->getGrContext() == nullptr) { + // Recording to picture, don't use the SkSurface which won't work off of renderthread. + Bitmap& bitmap = getBitmapUpdateIfDirty(); + SkBitmap skiaBitmap; + bitmap.getSkBitmap(&skiaBitmap); + + int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); + int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); + canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds, + &paint, SkCanvas::kFast_SrcRectConstraint); + return; + } + 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 +584,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(); } @@ -597,12 +611,7 @@ void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) { bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) { if (!canReuseBitmap(cache.bitmap.get(), width, height)) { -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - sk_sp<SkColorSpace> colorSpace = nullptr; -#else - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); -#endif - SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); cache.bitmap = Bitmap::allocateHeapBitmap(info); return true; } @@ -621,7 +630,81 @@ void Tree::onPropertyChanged(TreeProperties* prop) { } } -}; // namespace VectorDrawable +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 -}; // namespace android +} // namespace uirenderer +} // namespace android 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..28cabb9be0f1 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,47 +73,7 @@ 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 +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VERTEX_H diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h index 613cf4af64b2..6543a2251f7a 100644 --- a/libs/hwui/VertexBuffer.h +++ b/libs/hwui/VertexBuffer.h @@ -174,7 +174,7 @@ private: void (*mCleanupIndexMethod)(void*); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_VERTEX_BUFFER_H 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/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp new file mode 100644 index 000000000000..68541b4b31f0 --- /dev/null +++ b/libs/hwui/WebViewFunctorManager.cpp @@ -0,0 +1,195 @@ +/* + * 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 "WebViewFunctorManager.h" + +#include <private/hwui/WebViewFunctor.h> +#include "Properties.h" +#include "renderthread/RenderThread.h" + +#include <log/log.h> +#include <utils/Trace.h> +#include <atomic> + +namespace android::uirenderer { + +RenderMode WebViewFunctor_queryPlatformRenderMode() { + auto pipelineType = Properties::getRenderPipelineType(); + switch (pipelineType) { + case RenderPipelineType::SkiaGL: + return RenderMode::OpenGL_ES; + case RenderPipelineType::SkiaVulkan: + return RenderMode::Vulkan; + default: + LOG_ALWAYS_FATAL("Unknown render pipeline type: %d", (int)pipelineType); + } +} + +int WebViewFunctor_create(void* data, const WebViewFunctorCallbacks& prototype, + RenderMode functorMode) { + if (functorMode != RenderMode::OpenGL_ES && functorMode != RenderMode::Vulkan) { + ALOGW("Unknown rendermode %d", (int)functorMode); + return -1; + } + if (functorMode == RenderMode::Vulkan && + WebViewFunctor_queryPlatformRenderMode() != RenderMode::Vulkan) { + ALOGW("Unable to map from GLES platform to a vulkan functor"); + return -1; + } + return WebViewFunctorManager::instance().createFunctor(data, prototype, functorMode); +} + +void WebViewFunctor_release(int functor) { + WebViewFunctorManager::instance().releaseFunctor(functor); +} + +static std::atomic_int sNextId{1}; + +WebViewFunctor::WebViewFunctor(void* data, const WebViewFunctorCallbacks& callbacks, + RenderMode functorMode) + : mData(data) { + mFunctor = sNextId++; + mCallbacks = callbacks; + mMode = functorMode; +} + +WebViewFunctor::~WebViewFunctor() { + destroyContext(); + + ATRACE_NAME("WebViewFunctor::onDestroy"); + mCallbacks.onDestroyed(mFunctor, mData); +} + +void WebViewFunctor::sync(const WebViewSyncData& syncData) const { + ATRACE_NAME("WebViewFunctor::sync"); + mCallbacks.onSync(mFunctor, mData, syncData); +} + +void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) { + ATRACE_NAME("WebViewFunctor::drawGl"); + if (!mHasContext) { + mHasContext = true; + } + mCallbacks.gles.draw(mFunctor, mData, drawInfo); +} + +void WebViewFunctor::initVk(const VkFunctorInitParams& params) { + ATRACE_NAME("WebViewFunctor::initVk"); + if (!mHasContext) { + mHasContext = true; + } else { + return; + } + mCallbacks.vk.initialize(mFunctor, mData, params); +} + +void WebViewFunctor::drawVk(const VkFunctorDrawParams& params) { + ATRACE_NAME("WebViewFunctor::drawVk"); + mCallbacks.vk.draw(mFunctor, mData, params); +} + +void WebViewFunctor::postDrawVk() { + ATRACE_NAME("WebViewFunctor::postDrawVk"); + mCallbacks.vk.postDraw(mFunctor, mData); +} + +void WebViewFunctor::destroyContext() { + if (mHasContext) { + mHasContext = false; + ATRACE_NAME("WebViewFunctor::onContextDestroyed"); + mCallbacks.onContextDestroyed(mFunctor, mData); + + // grContext may be null in unit tests. + auto* grContext = renderthread::RenderThread::getInstance().getGrContext(); + if (grContext) grContext->resetContext(); + } +} + +WebViewFunctorManager& WebViewFunctorManager::instance() { + static WebViewFunctorManager sInstance; + return sInstance; +} + +int WebViewFunctorManager::createFunctor(void* data, const WebViewFunctorCallbacks& callbacks, + RenderMode functorMode) { + auto object = std::make_unique<WebViewFunctor>(data, callbacks, functorMode); + int id = object->id(); + auto handle = object->createHandle(); + { + std::lock_guard _lock{mLock}; + mActiveFunctors.push_back(std::move(handle)); + mFunctors.push_back(std::move(object)); + } + return id; +} + +void WebViewFunctorManager::releaseFunctor(int functor) { + sp<WebViewFunctor::Handle> toRelease; + { + std::lock_guard _lock{mLock}; + for (auto iter = mActiveFunctors.begin(); iter != mActiveFunctors.end(); iter++) { + if ((*iter)->id() == functor) { + toRelease = std::move(*iter); + mActiveFunctors.erase(iter); + break; + } + } + } +} + +void WebViewFunctorManager::onContextDestroyed() { + // WARNING: SKETCHY + // Because we know that we always remove from mFunctors on RenderThread, the same + // thread that always invokes onContextDestroyed, we know that the functor pointers + // will remain valid without the lock held. + // However, we won't block new functors from being added in the meantime. + mLock.lock(); + const size_t size = mFunctors.size(); + WebViewFunctor* toDestroyContext[size]; + for (size_t i = 0; i < size; i++) { + toDestroyContext[i] = mFunctors[i].get(); + } + mLock.unlock(); + for (size_t i = 0; i < size; i++) { + toDestroyContext[i]->destroyContext(); + } +} + +void WebViewFunctorManager::destroyFunctor(int functor) { + std::unique_ptr<WebViewFunctor> toRelease; + { + std::lock_guard _lock{mLock}; + for (auto iter = mFunctors.begin(); iter != mFunctors.end(); iter++) { + if ((*iter)->id() == functor) { + toRelease = std::move(*iter); + mFunctors.erase(iter); + break; + } + } + } +} + +sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) { + std::lock_guard _lock{mLock}; + for (auto& iter : mActiveFunctors) { + if (iter->id() == functor) { + return iter; + } + } + return nullptr; +} + +} // namespace android::uirenderer diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h new file mode 100644 index 000000000000..2846cb1f087b --- /dev/null +++ b/libs/hwui/WebViewFunctorManager.h @@ -0,0 +1,102 @@ +/* + * 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 <private/hwui/WebViewFunctor.h> +#include <renderthread/RenderProxy.h> + +#include <utils/LightRefBase.h> +#include <mutex> +#include <vector> + +namespace android::uirenderer { + +class WebViewFunctorManager; + +class WebViewFunctor { +public: + WebViewFunctor(void* data, const WebViewFunctorCallbacks& callbacks, RenderMode functorMode); + ~WebViewFunctor(); + + class Handle : public LightRefBase<Handle> { + public: + ~Handle() { renderthread::RenderProxy::destroyFunctor(id()); } + + int id() const { return mReference.id(); } + + void sync(const WebViewSyncData& syncData) const { mReference.sync(syncData); } + + void drawGl(const DrawGlInfo& drawInfo) const { mReference.drawGl(drawInfo); } + + void initVk(const VkFunctorInitParams& params) { mReference.initVk(params); } + + void drawVk(const VkFunctorDrawParams& params) { mReference.drawVk(params); } + + void postDrawVk() { mReference.postDrawVk(); } + + private: + friend class WebViewFunctor; + + Handle(WebViewFunctor& ref) : mReference(ref) {} + + WebViewFunctor& mReference; + }; + + int id() const { return mFunctor; } + void sync(const WebViewSyncData& syncData) const; + void drawGl(const DrawGlInfo& drawInfo); + void initVk(const VkFunctorInitParams& params); + void drawVk(const VkFunctorDrawParams& params); + void postDrawVk(); + void destroyContext(); + + sp<Handle> createHandle() { + LOG_ALWAYS_FATAL_IF(mCreatedHandle); + mCreatedHandle = true; + return sp<Handle>{new Handle(*this)}; + } + +private: + WebViewFunctorCallbacks mCallbacks; + void* const mData; + int mFunctor; + RenderMode mMode; + bool mHasContext = false; + bool mCreatedHandle = false; +}; + +class WebViewFunctorManager { +public: + static WebViewFunctorManager& instance(); + + int createFunctor(void* data, const WebViewFunctorCallbacks& callbacks, RenderMode functorMode); + void releaseFunctor(int functor); + void onContextDestroyed(); + void destroyFunctor(int functor); + + sp<WebViewFunctor::Handle> handleFor(int functor); + +private: + WebViewFunctorManager() = default; + ~WebViewFunctorManager() = default; + + std::mutex mLock; + std::vector<std::unique_ptr<WebViewFunctor>> mFunctors; + std::vector<sp<WebViewFunctor::Handle>> mActiveFunctors; +}; + +} // namespace android::uirenderer 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..219d04055eae 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> @@ -32,19 +32,18 @@ #include <SkCanvas.h> #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); @@ -76,31 +75,6 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) { return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap); } -static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { - void* addr = calloc(size, 1); - if (!addr) { - return nullptr; - } - return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); -} - -sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { - return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap); -} - -sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { - return allocateBitmap(bitmap, &android::allocateHeapBitmap); -} - -sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { - size_t size; - if (!computeAllocationSize(info.minRowBytes(), info.height(), &size)) { - LOG_ALWAYS_FATAL("trying to allocate too large bitmap"); - return nullptr; - } - return android::allocateHeapBitmap(size, info, info.minRowBytes()); -} - sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { // Create new ashmem region with read/write priv int fd = ashmem_create_region("bitmap", size); @@ -122,6 +96,31 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); } +sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) { + return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap); +} + +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { + return allocateBitmap(bitmap, &Bitmap::allocateHeapBitmap); +} + +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { + size_t size; + if (!computeAllocationSize(info.minRowBytes(), info.height(), &size)) { + LOG_ALWAYS_FATAL("trying to allocate too large bitmap"); + return nullptr; + } + return allocateHeapBitmap(size, info, info.minRowBytes()); +} + +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { + void* addr = calloc(size, 1); + if (!addr) { + return nullptr; + } + return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); +} + void FreePixelRef(void* addr, void* context) { auto pixelRef = (SkPixelRef*)context; pixelRef->unref(); @@ -133,16 +132,37 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) pixelRef.rowBytes())); } -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)) { + +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType, + sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType, + BitmapPalette palette) { + SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), + colorType, alphaType, colorSpace); + return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette)); +} + +sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly) { + if (info.colorType() == kUnknown_SkColorType) { + LOG_ALWAYS_FATAL("unknown bitmap configuration"); return nullptr; } - SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType, - SkColorSpace::MakeSRGB()); - return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); + + if (!addr) { + // Map existing ashmem region if not already mapped. + int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE); + size = ashmem_get_size_region(fd); + addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + return nullptr; + } + } + + sk_sp<Bitmap> bitmap(new Bitmap(addr, fd, size, info, rowBytes)); + if (readOnly) { + bitmap->setImmutable(); + } + return bitmap; } void Bitmap::setColorSpace(sk_sp<SkColorSpace> colorSpace) { @@ -197,18 +217,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 +251,6 @@ Bitmap::~Bitmap() { mPixelStorage.hardware.buffer = nullptr; break; } - - android::uirenderer::renderthread::RenderProxy::onBitmapDestroyed(getStableID()); } bool Bitmap::hasHardwareMipMap() const { @@ -269,6 +287,8 @@ size_t Bitmap::getAllocationByteCount() const { switch (mPixelStorageType) { case PixelStorageType::Heap: return mPixelStorage.heap.size; + case PixelStorageType::Ashmem: + return mPixelStorage.ashmem.size; default: return rowBytes() * height(); } @@ -287,21 +307,9 @@ 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); - if (mInfo.colorSpace()) { - sk_sp<SkPixelRef> pixelRef = sk_ref_sp(outBitmap->pixelRef()); - outBitmap->setInfo(mInfo); - outBitmap->setPixelRef(std::move(pixelRef), 0, 0); - } + outBitmap->allocPixels(mInfo); + uirenderer::renderthread::RenderProxy::copyHWBitmapInto(this, outBitmap); return; } outBitmap->setInfo(mInfo, rowBytes()); @@ -320,24 +328,102 @@ GraphicBuffer* Bitmap::graphicBuffer() { return nullptr; } -sk_sp<SkImage> Bitmap::makeImage(sk_sp<SkColorFilter>* outputColorFilter) { +sk_sp<SkImage> Bitmap::makeImage() { 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()) { - *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..dd98b25ac7e8 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; @@ -46,28 +54,33 @@ typedef void (*FreeFunc)(void* addr, void* context); class ANDROID_API Bitmap : public SkPixelRef { public: + /* The allocate factories not only construct the Bitmap object but also allocate the + * backing store whose type is determined by the specific method that is called. + * + * The factories that accept SkBitmap* as a param will modify those params by + * installing the returned bitmap as their SkPixelRef. + * + * The factories that accept const SkBitmap& as a param will copy the contents of the + * provided bitmap into the newly allocated buffer. + */ + static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); + static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& bitmap); static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info); - static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap); - - static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); - static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info, - size_t rowBytes); - - static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer); - + /* The createFrom factories construct a new Bitmap object by wrapping the already allocated + * memory that is provided as an input param. + */ + static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, + SkColorType colorType, + sk_sp<SkColorSpace> colorSpace, + SkAlphaType alphaType = kPremul_SkAlphaType, + BitmapPalette palette = BitmapPalette::Unknown); + static sk_sp<Bitmap> createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly); static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&); - Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); - 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); - - 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); @@ -90,22 +103,41 @@ public: bool isHardware() const { return mPixelStorageType == PixelStorageType::Hardware; } + PixelStorageType pixelStorageType() const { return mPixelStorageType; } + GraphicBuffer* graphicBuffer(); /** * Creates or returns a cached SkImage and is safe to be invoked from either * the UI or RenderThread. * - * @param outputColorFilter is a required param that will be populated by - * this function if the bitmap's colorspace is not sRGB. If populated the - * filter will convert colors from the bitmaps colorspace into sRGB. It - * is the callers responsibility to use this colorFilter when drawing - * this image into any destination that is presumed to be sRGB (i.e. a - * buffer that has no colorspace defined). */ - sk_sp<SkImage> makeImage(sk_sp<SkColorFilter>* outputColorFilter); + sk_sp<SkImage> makeImage(); + + 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: + static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + + Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); + 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, BitmapPalette palette); + virtual ~Bitmap(); void* getStorage() const; @@ -113,6 +145,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..68aa7374fec4 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -19,20 +19,18 @@ #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" + +#include <SkFontMetrics.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, @@ -42,35 +40,29 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca canvas->drawRect(left, top, right, bottom, paint); } -void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { - uint32_t flags; - SkDrawFilter* drawFilter = getDrawFilter(); - if (drawFilter) { - SkPaint paintCopy(paint); - drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); - flags = paintCopy.getFlags(); - } else { - flags = paint.getFlags(); - } - if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { +void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) { + // paint has already been filtered by our caller, so we can ignore any filter + const bool strikeThru = paint.isStrikeThru(); + const bool underline = paint.isUnderline(); + if (strikeThru || underline) { const SkScalar left = x; const SkScalar right = x + length; - if (flags & SkPaint::kUnderlineText_ReserveFlag) { - Paint::FontMetrics metrics; - paint.getFontMetrics(&metrics); + const float textSize = paint.getSkFont().getSize(); + if (underline) { + SkFontMetrics metrics; + paint.getSkFont().getMetrics(&metrics); SkScalar position; if (!metrics.hasUnderlinePosition(&position)) { - position = paint.getTextSize() * Paint::kStdUnderline_Top; + position = textSize * Paint::kStdUnderline_Top; } SkScalar thickness; if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + thickness = textSize * Paint::kStdUnderline_Thickness; } const SkScalar top = y + position; drawStroke(left, right, top, thickness, paint, this); } - if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - const float textSize = paint.getTextSize(); + if (strikeThru) { const float position = textSize * Paint::kStdStrikeThru_Top; const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; const SkScalar top = y + position; @@ -79,19 +71,19 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& } } -static void simplifyPaint(int color, SkPaint* paint) { +static void simplifyPaint(int color, Paint* paint) { paint->setColor(color); paint->setShader(nullptr); paint->setColorFilter(nullptr); paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); + paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); } class DrawTextFunctor { public: - DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x, + DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, float y, minikin::MinikinRect& bounds, float totalAdvance) : layout(layout) , canvas(canvas) @@ -127,14 +119,14 @@ public: bool darken = channelSum < (128 * 3); // outline - SkPaint outlinePaint(paint); + Paint outlinePaint(paint); simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); // inner - SkPaint innerPaint(paint); + Paint innerPaint(paint); simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); innerPaint.setStyle(SkPaint::kFill_Style); canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop, @@ -149,22 +141,21 @@ public: private: const minikin::Layout& layout; Canvas* canvas; - const SkPaint& paint; + const Paint& paint; float x; float y; minikin::MinikinRect& bounds; 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 +174,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 +238,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..ac8db216b059 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,11 +74,10 @@ typedef uint32_t Flags; } // namespace SaveFlags namespace uirenderer { -class SkiaCanvasProxy; namespace VectorDrawable { class Tree; -}; -}; +} +} typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc; @@ -170,6 +178,9 @@ public: virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0; virtual void callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) = 0; + virtual void drawWebViewFunctor(int /*functor*/) { + LOG_ALWAYS_FATAL("Not supported"); + } // ---------------------------------------------------------------------------- // Canvas state operations @@ -180,11 +191,13 @@ public: virtual int save(SaveFlags::Flags flags) = 0; virtual void restore() = 0; virtual void restoreToCount(int saveCount) = 0; + virtual void restoreUnclippedLayer(int saveCount, const SkPaint& paint) = 0; virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) = 0; virtual int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, SaveFlags::Flags flags) = 0; + virtual int saveUnclippedLayer(int, int, int, int) = 0; // Matrix virtual void getMatrix(SkMatrix* outMatrix) const = 0; @@ -205,8 +218,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 +241,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,35 +283,44 @@ 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: - void drawTextDecorations(float x, float y, float length, const SkPaint& paint); + void drawTextDecorations(float x, float y, float length, const Paint& paint); /** * glyphFunc: valid only for the duration of the call and should not be cached. * drawText: count is of glyphs * totalAdvance: used to define width of text decorations (underlines, strikethroughs). */ - virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x, + virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) = 0; virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, - const SkPaint& paint, const SkPath& path, size_t start, + const Paint& paint, const SkPath& path, size_t start, size_t end) = 0; static int sApiLevel; friend class DrawTextFunctor; friend class DrawTextOnPathFunctor; - friend class uirenderer::SkiaCanvasProxy; }; -}; // namespace android +} // namespace android diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 5d33860bab6b..6a12a203b9f8 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -17,55 +17,71 @@ #include "MinikinSkia.h" #include <SkFontDescriptor.h> +#include <SkFont.h> +#include <SkFontMetrics.h> #include <SkFontMgr.h> -#include <SkPaint.h> #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) {} - -static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint, - const minikin::MinikinPaint& paint, - const minikin::FontFakery& fakery) { - skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); - skPaint->setTextSize(paint.size); - skPaint->setTextScaleX(paint.scaleX); - skPaint->setTextSkewX(paint.skewX); - MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); + , mAxes(axes) + , mFilePath(filePath) {} + +static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery) { + skFont->setSize(paint.size); + skFont->setScaleX(paint.scaleX); + skFont->setSkewX(paint.skewX); + MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags); // Apply font fakery on top of user-supplied flags. - MinikinFontSkia::populateSkPaint(skPaint, font, fakery); + MinikinFontSkia::populateSkFont(skFont, font, fakery); } float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkScalar skWidth; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, &skWidth); #ifdef VERBOSE ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth); #endif return skWidth; } +void MinikinFontSkia::GetHorizontalAdvances(uint16_t* glyph_ids, uint32_t count, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery, + float* outAdvances) const { + SkFont skFont; + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(glyph_ids, count, outAdvances); +} + void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; + SkFont skFont; uint16_t glyph16 = glyph_id; SkRect skBounds; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds); + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + skFont.getWidths(&glyph16, 1, nullptr, &skBounds); bounds->mLeft = skBounds.fLeft; bounds->mTop = skBounds.fTop; bounds->mRight = skBounds.fRight; @@ -75,13 +91,12 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const { - SkPaint skPaint; - MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - SkPaint::FontMetrics metrics; - skPaint.getFontMetrics(&metrics); + SkFont skFont; + MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery); + SkFontMetrics metrics; + skFont.getMetrics(&metrics); extent->ascent = metrics.fAscent; extent->descent = metrics.fDescent; - extent->line_gap = metrics.fLeading; } SkTypeface* MinikinFontSkia::GetSkTypeface() const { @@ -127,33 +142,40 @@ 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(); - // select only flags that might affect text layout - flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | - SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag | - SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag | - SkPaint::kVerticalText_Flag); - flags |= (hinting << 16); +// hinting<<16 | edging<<8 | bools:5bits +uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) { + uint32_t flags = (unsigned)font.getHinting() << 16; + flags |= (unsigned)font.getEdging() << 8; + flags |= font.isEmbolden() << minikin::Embolden_Shift; + flags |= font.isLinearMetrics() << minikin::LinearMetrics_Shift; + flags |= font.isSubpixel() << minikin::Subpixel_Shift; + flags |= font.isEmbeddedBitmaps() << minikin::EmbeddedBitmaps_Shift; + flags |= font.isForceAutoHinting() << minikin::ForceAutoHinting_Shift; return flags; } -void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { - paint->setFlags(paintFlags & SkPaint::kAllFlags); - paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16)); +void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) { + // We store hinting in the top 16 bits (only need 2 of them) + font->setHinting((SkFontHinting)(flags >> 16)); + // We store edging in bits 8:15 (only need 2 of them) + font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF)); + font->setEmbolden( (flags & minikin::Embolden_Flag) != 0); + font->setLinearMetrics( (flags & minikin::LinearMetrics_Flag) != 0); + font->setSubpixel( (flags & minikin::Subpixel_Flag) != 0); + font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0); + font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0); } -void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, - minikin::FontFakery fakery) { - paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); - paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold()); +void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font, + minikin::FontFakery fakery) { + skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface()); + skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold()); if (fakery.isFakeItalic()) { - paint->setTextSkewX(paint->getTextSkewX() - 0.25f); + skFont->setSkewX(skFont->getSkewX() - 0.25f); } } } diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index d1565986304f..90f7d48a47ee 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -21,19 +21,25 @@ #include <cutils/compiler.h> #include <minikin/MinikinFont.h> -class SkPaint; +class SkFont; class SkTypeface; 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; + void GetHorizontalAdvances(uint16_t* glyph_ids, uint32_t count, + const minikin::MinikinPaint& paint, + const minikin::FontFakery& fakery, + float* outAdvances) const override; + void GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const override; @@ -48,16 +54,17 @@ 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; - static uint32_t packPaintFlags(const SkPaint* paint); - static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); + static uint32_t packFontFlags(const SkFont&); + static void unpackFontFlags(SkFont*, uint32_t fontFlags); // set typeface and fake bold/italic parameters - static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font, - minikin::FontFakery fakery); + static void populateSkFont(SkFont*, const minikin::MinikinFont* font, + minikin::FontFakery fakery); private: sk_sp<SkTypeface> mTypeface; @@ -68,6 +75,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..5f6b53ac767f 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -30,16 +30,17 @@ namespace android { minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, const Typeface* typeface) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + const SkFont& font = paint->getSkFont(); minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection); /* Prepare minikin Paint */ minikinPaint.size = - paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize()); - minikinPaint.scaleX = paint->getTextScaleX(); - minikinPaint.skewX = paint->getTextSkewX(); + font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize()); + minikinPaint.scaleX = font.getScaleX(); + minikinPaint.skewX = font.getSkewX(); minikinPaint.letterSpacing = paint->getLetterSpacing(); minikinPaint.wordSpacing = paint->getWordSpacing(); - minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint); + minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font); minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; @@ -48,23 +49,24 @@ 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::HyphenEdit hyphenEdit = static_cast<minikin::HyphenEdit>(paint->getHyphenEdit()); - const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); - const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + const minikin::Range contextRange(contextStart, contextStart + contextCount); + const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); + const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); 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, @@ -73,13 +75,11 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); const minikin::U16StringPiece textBuf(buf, bufSize); const minikin::Range range(start, start + count); - const minikin::HyphenEdit hyphenEdit = static_cast<minikin::HyphenEdit>(paint->getHyphenEdit()); - const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); - const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); + const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); 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..cbf409504675 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, @@ -62,27 +63,29 @@ public: // f is a functor of type void f(size_t start, size_t end); template <typename F> ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) { - float saveSkewX = paint->getTextSkewX(); - bool savefakeBold = paint->isFakeBoldText(); + float saveSkewX = paint->getSkFont().getSkewX(); + bool savefakeBold = paint->getSkFont().isEmbolden(); const minikin::MinikinFont* curFont = nullptr; size_t start = 0; size_t nGlyphs = layout.nGlyphs(); for (size_t i = 0; i < nGlyphs; i++) { const minikin::MinikinFont* nextFont = layout.getFont(i); if (i > 0 && nextFont != curFont) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, i); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); start = i; } curFont = nextFont; } if (nGlyphs > start) { - MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + SkFont* skfont = &paint->getSkFont(); + MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start)); f(start, nGlyphs); - paint->setTextSkewX(saveSkewX); - paint->setFakeBoldText(savefakeBold); + skfont->setSkewX(saveSkewX); + skfont->setEmbolden(savefakeBold); } } }; diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index e4d81c16ada5..9b2fa9df1e8f 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -21,10 +21,13 @@ #include <cutils/compiler.h> +#include <SkFont.h> #include <SkPaint.h> #include <string> #include <minikin/FontFamily.h> +#include <minikin/FamilyVariant.h> +#include <minikin/Hyphenator.h> namespace android { @@ -45,7 +48,6 @@ public: Paint(); Paint(const Paint& paint); - Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor) ~Paint(); Paint& operator=(const Paint& other); @@ -53,6 +55,17 @@ public: friend bool operator==(const Paint& a, const Paint& b); friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); } + SkFont& getSkFont() { return mFont; } + const SkFont& getSkFont() const { return mFont; } + + // These shadow the methods on SkPaint, but we need to so we can keep related + // attributes in-sync. + + void reset(); + void setAntiAlias(bool); + + // End method shadowing + void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; } float getLetterSpacing() const { return mLetterSpacing; } @@ -73,30 +86,82 @@ 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; } + void setStartHyphenEdit(uint32_t startHyphen) { + mHyphenEdit = minikin::packHyphenEdit( + static_cast<minikin::StartHyphenEdit>(startHyphen), + minikin::endHyphenEdit(mHyphenEdit)); + } - uint32_t getHyphenEdit() const { return mHyphenEdit; } + void setEndHyphenEdit(uint32_t endHyphen) { + mHyphenEdit = minikin::packHyphenEdit( + minikin::startHyphenEdit(mHyphenEdit), + static_cast<minikin::EndHyphenEdit>(endHyphen)); + } + + minikin::StartHyphenEdit getStartHyphenEdit() const { + return minikin::startHyphenEdit(mHyphenEdit); + } + + minikin::EndHyphenEdit getEndHyphenEdit() const { + return minikin::endHyphenEdit(mHyphenEdit); + } void setAndroidTypeface(Typeface* typeface) { mTypeface = typeface; } 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; } + + bool isStrikeThru() const { return mStrikeThru; } + void setStrikeThru(bool st) { mStrikeThru = st; } + + bool isUnderline() const { return mUnderline; } + void setUnderline(bool u) { mUnderline = u; } + + bool isDevKern() const { return mDevKern; } + void setDevKern(bool d) { mDevKern = d; } + + // The Java flags (Paint.java) no longer fit into the native apis directly. + // These methods handle converting to and from them and the native representations + // in android::Paint. + + uint32_t getJavaFlags() const; + void setJavaFlags(uint32_t); + + // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags + // that are meant for SkFont or Paint (e.g. underline, strikethru) + // The only respected flags are : [ antialias, dither, filterBitmap ] + static uint32_t GetSkPaintJavaFlags(const SkPaint&); + static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags); + private: + SkFont mFont; + 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; + bool mStrikeThru = false; + bool mUnderline = false; + bool mDevKern = false; }; } // namespace android diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h new file mode 100644 index 000000000000..0e7b61977000 --- /dev/null +++ b/libs/hwui/hwui/PaintFilter.h @@ -0,0 +1,20 @@ +#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; + virtual void filterFullPaint(Paint*) = 0; +}; + +} // namespace android + +#endif diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index ae9c475d09d4..2f2d575bca29 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,30 +24,34 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) { + // SkPaint::antialiasing defaults to false, but + // SkFont::edging defaults to kAntiAlias. To keep them + // insync, we manually set the font to kAilas. + mFont.setEdging(SkFont::Edging::kAlias); +} Paint::Paint(const Paint& paint) : SkPaint(paint) + , mFont(paint.mFont) , mLetterSpacing(paint.mLetterSpacing) , mWordSpacing(paint.mWordSpacing) , mFontFeatureSettings(paint.mFontFeatureSettings) , mMinikinLocaleListId(paint.mMinikinLocaleListId) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) - , mTypeface(paint.mTypeface) {} + , mTypeface(paint.mTypeface) + , mAlign(paint.mAlign) + , mStrikeThru(paint.mStrikeThru) + , mUnderline(paint.mUnderline) + , mDevKern(paint.mDevKern) {} -Paint::Paint(const SkPaint& paint) - : SkPaint(paint) - , mLetterSpacing(0) - , mWordSpacing(0) - , mFontFeatureSettings() - , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {} Paint::~Paint() {} Paint& Paint::operator=(const Paint& other) { SkPaint::operator=(other); + mFont = other.mFont; mLetterSpacing = other.mLetterSpacing; mWordSpacing = other.mWordSpacing; mFontFeatureSettings = other.mFontFeatureSettings; @@ -55,15 +59,137 @@ Paint& Paint::operator=(const Paint& other) { mFamilyVariant = other.mFamilyVariant; mHyphenEdit = other.mHyphenEdit; mTypeface = other.mTypeface; + mAlign = other.mAlign; + mStrikeThru = other.mStrikeThru; + mUnderline = other.mUnderline; + mDevKern = other.mDevKern; return *this; } bool operator==(const Paint& a, const Paint& b) { return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && + a.mFont == b.mFont && a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && 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 && + a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && + a.mDevKern == b.mDevKern; +} + +void Paint::reset() { + SkPaint::reset(); + + mFont = SkFont(); + mFont.setEdging(SkFont::Edging::kAlias); + + mStrikeThru = false; + mUnderline = false; + mDevKern = false; +} + +void Paint::setAntiAlias(bool aa) { + // Java does not support/understand subpixel(lcd) antialiasing + SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias); + // JavaPaint antialiasing affects both the SkPaint and SkFont settings. + SkPaint::setAntiAlias(aa); + mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias); +} + +////////////////// Java flags compatibility ////////////////// + +/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really + match up with skia anymore, so we have to do some shuffling in get/set flags() + + 3 flags apply to SkPaint (antialias, dither, filter -> enum) + 5 flags (merged with antialias) are for SkFont + 2 flags are for minikin::Paint (underline and strikethru) +*/ + +// flags relating to SkPaint +static const uint32_t sAntiAliasFlag = 0x01; // affects paint and font-edging +static const uint32_t sFilterBitmapFlag = 0x02; // maps to enum +static const uint32_t sDitherFlag = 0x04; +// flags relating to SkFont +static const uint32_t sFakeBoldFlag = 0x020; +static const uint32_t sLinearMetrics = 0x040; +static const uint32_t sSubpixelMetrics = 0x080; +static const uint32_t sEmbeddedBitmaps = 0x400; +static const uint32_t sForceAutoHinting = 0x800; +// flags related to minikin::Paint +static const uint32_t sUnderlineFlag = 0x08; +static const uint32_t sStrikeThruFlag = 0x10; +// flags no longer supported on native side (but mirrored for compatibility) +static const uint32_t sDevKernFlag = 0x100; + +static uint32_t paintToLegacyFlags(const SkPaint& paint) { + uint32_t flags = 0; + flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag; + flags |= -(int)paint.isDither() & sDitherFlag; + if (paint.getFilterQuality() != kNone_SkFilterQuality) { + flags |= sFilterBitmapFlag; + } + return flags; } + +static uint32_t fontToLegacyFlags(const SkFont& font) { + uint32_t flags = 0; + flags |= -(int)font.isEmbolden() & sFakeBoldFlag; + flags |= -(int)font.isLinearMetrics() & sLinearMetrics; + flags |= -(int)font.isSubpixel() & sSubpixelMetrics; + flags |= -(int)font.isEmbeddedBitmaps() & sEmbeddedBitmaps; + flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting; + return flags; +} + +static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) { + paint->setAntiAlias((flags & sAntiAliasFlag) != 0); + paint->setDither ((flags & sDitherFlag) != 0); + + if (flags & sFilterBitmapFlag) { + paint->setFilterQuality(kLow_SkFilterQuality); + } else { + paint->setFilterQuality(kNone_SkFilterQuality); + } +} + +static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) { + font->setEmbolden ((flags & sFakeBoldFlag) != 0); + font->setLinearMetrics ((flags & sLinearMetrics) != 0); + font->setSubpixel ((flags & sSubpixelMetrics) != 0); + font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0); + font->setForceAutoHinting((flags & sForceAutoHinting) != 0); + + if (flags & sAntiAliasFlag) { + font->setEdging(SkFont::Edging::kAntiAlias); + } else { + font->setEdging(SkFont::Edging::kAlias); + } +} + +uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) { + return paintToLegacyFlags(paint); +} + +void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) { + applyLegacyFlagsToPaint(flags, paint); +} + +uint32_t Paint::getJavaFlags() const { + uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont); + flags |= -(int)mStrikeThru & sStrikeThruFlag; + flags |= -(int)mUnderline & sUnderlineFlag; + flags |= -(int)mDevKern & sDevKernFlag; + return flags; +} + +void Paint::setJavaFlags(uint32_t flags) { + applyLegacyFlagsToPaint(flags, this); + applyLegacyFlagsToFont(flags, &mFont); + mStrikeThru = (flags & sStrikeThruFlag) != 0; + mUnderline = (flags & sUnderlineFlag) != 0; + mDevKern = (flags & sDevKernFlag) != 0; +} + } // 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/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h index efef6de2a9e1..bf19655825b3 100644 --- a/libs/hwui/pipeline/skia/AnimatedDrawables.h +++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h @@ -79,6 +79,6 @@ private: sp<uirenderer::CanvasPropertyPaint> mPaint; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h index 1f83d1a201b0..0eb526af127a 100644 --- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h +++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h @@ -82,28 +82,6 @@ protected: mOutput << mIdent << "drawDRRect" << std::endl; } - void onDrawText(const void*, size_t, SkScalar, SkScalar, const SkPaint&) override { - mOutput << mIdent << "drawText" << std::endl; - } - - void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override { - mOutput << mIdent << "drawPosText" << std::endl; - } - - void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override { - 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; - } - void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override { mOutput << mIdent << "drawTextBlob" << std::endl; } @@ -143,7 +121,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 +140,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; @@ -177,6 +155,6 @@ private: std::string mIdent; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h new file mode 100644 index 000000000000..cf2f93b95e71 --- /dev/null +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -0,0 +1,81 @@ +/* + * 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 <WebViewFunctorManager.h> +#include <utils/Functor.h> +#include <variant> + +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) + : mBounds(canvas->getLocalClipBounds()) + , mAnyFunctor(std::in_place_type<LegacyFunctor>, functor, listener) {} + + FunctorDrawable(int functor, SkCanvas* canvas) + : mBounds(canvas->getLocalClipBounds()) + , mAnyFunctor(std::in_place_type<NewFunctor>, functor) {} + + virtual ~FunctorDrawable() {} + + virtual void syncFunctor(const WebViewSyncData& data) const { + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->sync(data); + } else { + (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeSync, nullptr); + } + } + +protected: + virtual SkRect onGetBounds() override { return mBounds; } + + const SkRect mBounds; + + struct LegacyFunctor { + explicit LegacyFunctor(Functor* functor, GlFunctorLifecycleListener* listener) + : functor(functor), listener(listener) {} + Functor* functor; + sp<GlFunctorLifecycleListener> listener; + }; + + struct NewFunctor { + explicit NewFunctor(int functor) { + handle = WebViewFunctorManager::instance().handleFor(functor); + } + sp<WebViewFunctor::Handle> handle; + }; + + std::variant<NewFunctor, LegacyFunctor> mAnyFunctor; +}; + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 3684bc1e6a1f..a1b2b18195bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -17,30 +17,28 @@ #include "GLFunctorDrawable.h" #include <GrContext.h> #include <private/hwui/DrawGlInfo.h> +#include "FunctorDrawable.h" #include "GlFunctorLifecycleListener.h" +#include "GrBackendSurface.h" +#include "GrRenderTarget.h" +#include "GrRenderTargetContext.h" #include "RenderNode.h" #include "SkAndroidFrameworkUtils.h" #include "SkClipStack.h" #include "SkRect.h" -#include "GrBackendSurface.h" -#include "GrRenderTarget.h" -#include "GrRenderTargetContext.h" -#include "GrGLTypes.h" namespace android { namespace uirenderer { namespace skiapipeline { GLFunctorDrawable::~GLFunctorDrawable() { - if (mListener.get() != nullptr) { - mListener->onGlFunctorReleased(mFunctor); + if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) { + if (lp->listener) { + lp->listener->onGlFunctorReleased(lp->functor); + } } } -void GLFunctorDrawable::syncFunctor() const { - (*mFunctor)(DrawGlInfo::kModeSync, nullptr); -} - static void setScissor(int viewportHeight, const SkIRect& clip) { SkASSERT(!clip.isEmpty()); // transform to Y-flipped GL space, and prevent negatives @@ -50,40 +48,39 @@ static void setScissor(int viewportHeight, const SkIRect& clip) { } static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) { - GrRenderTargetContext *renderTargetContext = + GrRenderTargetContext* renderTargetContext = canvas->internal_private_accessTopLayerRenderTargetContext(); if (!renderTargetContext) { ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); return false; } - GrRenderTarget *renderTarget = renderTargetContext->accessRenderTarget(); + GrRenderTarget* renderTarget = renderTargetContext->accessRenderTarget(); if (!renderTarget) { ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); 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; } void GLFunctorDrawable::onDraw(SkCanvas* canvas) { if (canvas->getGrContext() == nullptr) { - SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface")); - return; - } - - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - canvas->clear(SK_ColorRED); + // We're dumping a picture, render a light-blue rectangle instead + // TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't + // seem to have a default typeface that works. We only ever use drawGlyphs, which + // requires going through minikin & hwui's canvas which we don't have here. + SkPaint paint; + paint.setColor(0xFF81D4FA); + canvas->drawRect(mBounds, paint); return; } @@ -102,21 +99,22 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { sk_sp<SkSurface> tmpSurface; // we are in a state where there is an unclipped saveLayer if (fboID != 0 && !surfaceBounds.contains(clipBounds)) { - // create an offscreen layer and clear it - SkImageInfo surfaceInfo = canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); - tmpSurface = SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, - surfaceInfo); + SkImageInfo surfaceInfo = + canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height()); + tmpSurface = + SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, 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 @@ -140,6 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.width = fboSize.width(); info.height = fboSize.height(); mat4.asColMajorf(&info.transform[0]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear // the stencil and/or draw the functor. @@ -151,7 +150,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { bool clearStencilAfterFunctor = false; if (CC_UNLIKELY(clipRegion.isComplex())) { // clear the stencil - //TODO: move stencil clear and canvas flush to SkAndroidFrameworkUtils::clipWithStencil + // TODO: move stencil clear and canvas flush to SkAndroidFrameworkUtils::clipWithStencil glDisable(GL_SCISSOR_TEST); glStencilMask(0x1); glClearStencil(0); @@ -170,7 +169,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // GL ops get inserted here if previous flush is missing, which could dirty the stencil bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas); - tmpCanvas->flush(); //need this flush for the single op that draws into the stencil + tmpCanvas->flush(); // need this flush for the single op that draws into the stencil // ensure that the framebuffer that the webview will render into is bound before after we // draw into the stencil @@ -195,7 +194,11 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { setScissor(info.height, clipRegion.getBounds()); } - (*mFunctor)(DrawGlInfo::kModeDraw, &info); + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->drawGl(info); + } else { + (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info); + } if (clearStencilAfterFunctor) { // clear stencil buffer as it may be used by Skia @@ -223,6 +226,6 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { } } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h index af57d7d33c2c..2ea4f67428bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h @@ -16,41 +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()) {} - virtual ~GLFunctorDrawable(); + using FunctorDrawable::FunctorDrawable; - void syncFunctor() const; + virtual ~GLFunctorDrawable(); protected: - virtual SkRect onGetBounds() override { return mBounds; } - virtual void onDraw(SkCanvas* canvas) override; - -private: - Functor* mFunctor; - sp<GlFunctorLifecycleListener> mListener; - const SkRect mBounds; + void onDraw(SkCanvas* canvas) override; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index ab1d5c2546ed..eed19420a78a 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -15,8 +15,7 @@ */ #include "LayerDrawable.h" -#include "GlLayer.h" -#include "VkLayer.h" +#include <utils/MathUtils.h> #include "GrBackendSurface.h" #include "SkColorFilter.h" @@ -30,112 +29,109 @@ 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); } } +// This is a less-strict matrix.isTranslate() that will still report being translate-only +// on imperceptibly small scaleX & scaleY values. +static bool isBasicallyTranslate(const SkMatrix& matrix) { + if (!matrix.isScaleTranslate()) return false; + return MathUtils::isOne(matrix.getScaleX()) && MathUtils::isOne(matrix.getScaleY()); +} + +static bool shouldFilter(const SkMatrix& matrix) { + if (!matrix.isScaleTranslate()) return true; + + // We only care about meaningful scale here + bool noScale = MathUtils::isOne(matrix.getScaleX()) && MathUtils::isOne(matrix.getScaleY()); + bool pixelAligned = + SkScalarIsInt(matrix.getTranslateX()) && SkScalarIsInt(matrix.getTranslateY()); + return !(noScale && pixelAligned); +} + 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()); - + paint.setColorFilter(layer->getColorFilter()); 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. // Integer translation is defined as when src rect and dst rect align fractionally. // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works // 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(skiaDestRect.fTop + totalMatrix[SkMatrix::kMTransY]) - == SkScalarFraction(srcRect.fTop); + bool isIntegerTranslate = + isBasicallyTranslate(totalMatrix) && + SkScalarFraction(skiaDestRect.fLeft + totalMatrix[SkMatrix::kMTransX]) == + SkScalarFraction(skiaSrcRect.fLeft) && + SkScalarFraction(skiaDestRect.fTop + totalMatrix[SkMatrix::kMTransY]) == + 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() - && SkScalarIsInt(totalMatrix[SkMatrix::kMTransX]) - && SkScalarIsInt(totalMatrix[SkMatrix::kMTransY]); - if (layer->getForceFilter() || !isIntegerTranslate) { + if (layer->getForceFilter() || shouldFilter(totalMatrix)) { paint.setFilterQuality(kLow_SkFilterQuality); } canvas->drawImage(layerImage.get(), 0, 0, &paint); @@ -146,9 +142,9 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer } } - return layerImage; + return layerImage != nullptr; } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h index 18d118405a39..7cd515ae9fcb 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.h +++ b/libs/hwui/pipeline/skia/LayerDrawable.h @@ -32,8 +32,8 @@ class LayerDrawable : public SkDrawable { public: explicit LayerDrawable(DeferredLayerUpdater* layerUpdater) : mLayerUpdater(layerUpdater) {} - static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, - const SkRect* dstRect = nullptr); + static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, const SkRect* srcRect, + const SkRect* dstRect, bool useLayerTransform); protected: virtual SkRect onGetBounds() override { @@ -45,6 +45,6 @@ private: sp<DeferredLayerUpdater> mLayerUpdater; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 6c04d7862979..1bd30eb5371b 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) { @@ -100,22 +115,36 @@ void RenderNodeDrawable::onDraw(SkCanvas* canvas) { } } +class MarkDraw { +public: + explicit MarkDraw(SkCanvas& canvas, RenderNode& node) : mCanvas(canvas), mNode(node) { + if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { + mNode.markDrawStart(mCanvas); + } + } + ~MarkDraw() { + if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { + mNode.markDrawEnd(mCanvas); + } + } + +private: + SkCanvas& mCanvas; + RenderNode& mNode; +}; + void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { RenderNode* renderNode = mRenderNode.get(); - if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { - SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight()); - canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr); - } + MarkDraw _marker{*canvas, *renderNode}; // We only respect the nothingToDraw check when we are composing a layer. This // ensures that we paint the layer even if it is not currently visible in the // event that the properties change and it becomes visible. if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) || - (renderNode->nothingToDraw() && mComposeLayer)) { + (renderNode->nothingToDraw() && mComposeLayer)) { return; } - SkASSERT(renderNode->getDisplayList()->isSkiaDL()); SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList(); SkAutoCanvasRestore acr(canvas, true); @@ -130,9 +159,9 @@ 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); + canvas->clipPath(*mProjectedDisplayList->mProjectedOutline->getPath()); } drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList); } @@ -144,10 +173,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 +188,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 +217,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()); @@ -208,8 +235,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // we need to restrict the portion of the surface drawn to the size of the renderNode. SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width()); SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height()); - canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), - bounds, bounds, &paint); + canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), bounds, + bounds, &paint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true; @@ -306,6 +333,6 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S } } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index ef21cd8a29b5..6ba8e599818c 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 @@ -150,6 +150,6 @@ private: SkiaDisplayList* mProjectedDisplayList = nullptr; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 25c51f2716e6..0a3c8f4347eb 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); @@ -199,6 +211,6 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0); } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h index 3c48d3604864..cfc0f9b258da 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: @@ -73,6 +74,6 @@ private: StartReorderBarrierDrawable* mStartBarrier; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 670074871c71..66aa8c203799 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -15,8 +15,11 @@ */ #include "ShaderCache.h" -#include <algorithm> +#include <GrContext.h> #include <log/log.h> +#include <openssl/sha.h> +#include <algorithm> +#include <array> #include <thread> #include "FileBlobCache.h" #include "Properties.h" @@ -28,8 +31,8 @@ namespace skiapipeline { // Cache size limits. static const size_t maxKeySize = 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 512 * 1024; +static const size_t maxValueSize = 512 * 1024; +static const size_t maxTotalSize = 1024 * 1024; ShaderCache::ShaderCache() { // There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header. @@ -41,7 +44,38 @@ 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 +84,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; } } @@ -83,7 +118,7 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { int maxTries = 3; while (valueSize > mObservedBlobValueSize && maxTries > 0) { mObservedBlobValueSize = std::min(valueSize, maxValueSize); - void *newValueBuffer = realloc(valueBuffer, mObservedBlobValueSize); + void* newValueBuffer = realloc(valueBuffer, mObservedBlobValueSize); if (!newValueBuffer) { free(valueBuffer); return nullptr; @@ -97,13 +132,25 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return nullptr; } if (valueSize > mObservedBlobValueSize) { - ALOGE("ShaderCache::load value size is too big %d", (int) valueSize); + ALOGE("ShaderCache::load value size is too big %d", (int)valueSize); free(valueBuffer); return nullptr; } 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); @@ -122,6 +169,24 @@ void ShaderCache::store(const SkData& key, const SkData& data) { const void* value = data.data(); BlobCache* bc = getBlobCacheLocked(); + if (mInStoreVkPipelineInProgress) { + if (mOldPipelineCacheSize == -1) { + // Record the initial pipeline cache size stored in the file. + mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0); + } + if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) { + // There has not been change in pipeline cache size. Stop trying to save. + mTryToStorePipelineCache = false; + return; + } + mNewPipelineCacheSize = valueSize; + } else { + mCacheDirty = true; + // If there are new shaders compiled, we probably have new pipeline state too. + // Store pipeline cache on the next flush. + mNewPipelineCacheSize = -1; + mTryToStorePipelineCache = true; + } bc->set(key.data(), keySize, value, valueSize); if (!mSavePending && mDeferredSaveDelay > 0) { @@ -129,16 +194,31 @@ 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(); + // Store file on disk if there a new shader or Vulkan pipeline cache size changed. + if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { + saveToDiskLocked(); + mOldPipelineCacheSize = mNewPipelineCacheSize; + mTryToStorePipelineCache = false; + mCacheDirty = false; } - mSavePending = false; }); deferredSaveThread.detach(); } } +void ShaderCache::onVkFrameFlushed(GrContext* context) { + { + std::lock_guard<std::mutex> lock(mMutex); + + if (!mInitialized || !mTryToStorePipelineCache) { + return; + } + } + mInStoreVkPipelineInProgress = true; + context->storeVkPipelineCacheData(); + mInStoreVkPipelineInProgress = false; +} + } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 27473d67bd1a..0898017d52a1 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -16,12 +16,12 @@ #pragma once +#include <GrContextOptions.h> #include <cutils/compiler.h> #include <memory> #include <mutex> #include <string> #include <vector> -#include <GrContextOptions.h> namespace android { @@ -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 @@ -66,6 +75,13 @@ public: */ void store(const SkData& key, const SkData& data) override; + /** + * "onVkFrameFlushed" tries to store Vulkan pipeline cache state. + * Pipeline cache is saved on disk only if the size of the data has changed or there was + * a new shader compiled. + */ + void onVkFrameFlushed(GrContext* context); + private: // Creation and (the lack of) destruction is handled internally. ShaderCache(); @@ -83,6 +99,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 +140,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. @@ -122,7 +160,7 @@ private: /** * "mObservedBlobValueSize" is the maximum value size observed by the cache reading function. */ - size_t mObservedBlobValueSize = 20*1024; + size_t mObservedBlobValueSize = 20 * 1024; /** * The time in seconds to wait before saving newly inserted cache entries. @@ -136,11 +174,43 @@ private: mutable std::mutex mMutex; /** + * If set to "true", the next call to onVkFrameFlushed, will invoke + * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk. + */ + bool mTryToStorePipelineCache = true; + + /** + * This flag is used by "ShaderCache::store" to distinguish between shader data and + * Vulkan pipeline data. + */ + bool mInStoreVkPipelineInProgress = false; + + /** + * "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used + * to prevent unnecessary disk writes, if the pipeline cache size has not changed. + */ + size_t mNewPipelineCacheSize = -1; + /** + * "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk. + */ + size_t mOldPipelineCacheSize = -1; + + /** + * "mCacheDirty" is true when there is new shader cache data, which is not saved to disk. + */ + bool mCacheDirty = false; + + /** * "sCache" is the singleton ShaderCache object. */ static ShaderCache sCache; - friend class ShaderCacheTestUtils; //used for unit testing + /** + * "sIDKey" is the cache key of the identity hash + */ + static constexpr uint8_t sIDKey = 0; + + friend class ShaderCacheTestUtils; // used for unit testing }; } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index 82179a37f5be..41bcfc25f5c1 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -22,20 +22,21 @@ #include "renderthread/CanvasContext.h" #include <SkImagePriv.h> +#include <SkPathOps.h> namespace android { namespace uirenderer { namespace skiapipeline { -void SkiaDisplayList::syncContents() { +void SkiaDisplayList::syncContents(const WebViewSyncData& data) { for (auto& functor : mChildFunctors) { - functor.syncFunctor(); + functor->syncFunctor(data); } for (auto& animatedImage : mAnimatedImages) { animatedImage->syncProperties(); } for (auto& vectorDrawable : mVectorDrawables) { - vectorDrawable->syncProperties(); + vectorDrawable.first->syncProperties(); } } @@ -51,6 +52,29 @@ void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) } } +static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) { + Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0}, + Vector3 {bounds.fRight, bounds.fTop, 0}, + Vector3 {bounds.fRight, bounds.fBottom, 0}, + Vector3 {bounds.fLeft, bounds.fBottom, 0}}; + float minX, minY, maxX, maxY; + bool first = true; + for (auto& point : points) { + mat.mapPoint3d(point); + if (first) { + minX = maxX = point.x; + minY = maxY = point.y; + first = false; + } else { + minX = std::min(minX, point.x); + minY = std::min(minY, point.y); + maxX = std::max(maxX, point.x); + maxY = std::max(maxY, point.y); + } + } + return SkRect::Make(screenSize).intersects(SkRect::MakeLTRB(minX, minY, maxX, maxY)); +} + bool SkiaDisplayList::prepareListAndChildren( TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer, std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) { @@ -73,7 +97,6 @@ bool SkiaDisplayList::prepareListAndChildren( RenderNode* childNode = child.getRenderNode(); Matrix4 mat4(child.getRecordedMatrix()); info.damageAccumulator->pushTransform(&mat4); - // TODO: a layer is needed if the canvas is rotated or has a non-rect clip info.hasBackwardProjectedNodes = false; childFn(childNode, observer, info, functorsNeedLayer); hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes; @@ -108,15 +131,23 @@ bool SkiaDisplayList::prepareListAndChildren( } } - for (auto& vectorDrawable : mVectorDrawables) { + for (auto& vectorDrawablePair : mVectorDrawables) { // If any vector drawable in the display list needs update, damage the node. + auto& vectorDrawable = vectorDrawablePair.first; if (vectorDrawable->isDirty()) { - isDirty = true; - static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline()) - ->getVectorDrawables() - ->push_back(vectorDrawable); + Matrix4 totalMatrix; + info.damageAccumulator->computeCurrentTransform(&totalMatrix); + Matrix4 canvasMatrix(vectorDrawablePair.second); + totalMatrix.multiply(canvasMatrix); + const SkRect& bounds = vectorDrawable->properties().getBounds(); + if (intersects(info.screenSize, totalMatrix, bounds)) { + isDirty = true; + static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline()) + ->getVectorDrawables() + ->push_back(vectorDrawable); + vectorDrawable->setPropertyChangeWillBeConsumed(true); + } } - vectorDrawable->setPropertyChangeWillBeConsumed(true); } return isDirty; } @@ -132,7 +163,6 @@ void SkiaDisplayList::reset() { mChildFunctors.clear(); mChildNodes.clear(); - projectionReceiveIndex = -1; allocator.~LinearAllocator(); new (&allocator) LinearAllocator(); } @@ -142,6 +172,6 @@ void SkiaDisplayList::output(std::ostream& output, uint32_t level) { mDisplayList.draw(&canvas); } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 818ec114a5b3..b79103787023 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -16,32 +16,37 @@ #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 "hwui/AnimatedImageDrawable.h" +#include "utils/LinearAllocator.h" +#include "utils/Pair.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 { -/** - * This class is intended to be self contained, but still subclasses from - * DisplayList to make it easier to support switching between the two at - * 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() + mDisplayList.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 +69,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 +104,7 @@ public: * NOTE: This function can be folded into RenderNode when we no longer need * to subclass from DisplayList */ - void syncContents() override; + void syncContents(const WebViewSyncData& data); /** * ONLY to be called by RenderNode::prepareTree in order to prepare this @@ -116,25 +121,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 +149,21 @@ public: * cannot relocate. */ std::deque<RenderNodeDrawable> mChildNodes; - std::deque<GLFunctorDrawable> mChildFunctors; + std::deque<FunctorDrawable*> mChildFunctors; std::vector<SkImage*> mMutableImages; - std::vector<VectorDrawableRoot*> mVectorDrawables; +private: + std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables; +public: + void appendVD(VectorDrawableRoot* r) { + appendVD(r, SkMatrix::I()); + } + + void appendVD(VectorDrawableRoot* r, const SkMatrix& mat) { + mVectorDrawables.push_back(Pair<VectorDrawableRoot*, SkMatrix>(r, mat)); + } + 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,14 +176,14 @@ 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 -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp index ea578cb3ec05..e48ecf490c56 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp @@ -21,16 +21,16 @@ namespace uirenderer { namespace skiapipeline { SkiaMemoryTracer::SkiaMemoryTracer(std::vector<ResourcePair> resourceMap, bool itemizeType) - : mResourceMap(resourceMap) - , mItemizeType(itemizeType) - , mTotalSize("bytes", 0) - , mPurgeableSize("bytes", 0) {} + : mResourceMap(resourceMap) + , mItemizeType(itemizeType) + , mTotalSize("bytes", 0) + , mPurgeableSize("bytes", 0) {} SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType) - : mCategoryKey(categoryKey) - , mItemizeType(itemizeType) - , mTotalSize("bytes", 0) - , mPurgeableSize("bytes", 0) {} + : mCategoryKey(categoryKey) + , mItemizeType(itemizeType) + , mTotalSize("bytes", 0) + , mPurgeableSize("bytes", 0) {} const char* SkiaMemoryTracer::mapName(const char* resourceName) { for (auto& resource : mResourceMap) { @@ -42,7 +42,7 @@ const char* SkiaMemoryTracer::mapName(const char* resourceName) { } void SkiaMemoryTracer::processElement() { - if(!mCurrentElement.empty()) { + if (!mCurrentElement.empty()) { // Only count elements that contain "size", other values just provide metadata. auto sizeResult = mCurrentValues.find("size"); if (sizeResult != mCurrentValues.end()) { @@ -136,8 +136,8 @@ void SkiaMemoryTracer::logOutput(String8& log) { for (const auto& typedValue : namedItem.second) { TraceValue traceValue = convertUnits(typedValue.second); const char* entry = (traceValue.count > 1) ? "entries" : "entry"; - log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, - traceValue.value, traceValue.units, traceValue.count, entry); + log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value, + traceValue.units, traceValue.count, entry); } } else { auto result = namedItem.second.find("size"); diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h index abf1f4b052ce..e9a7981ef028 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h @@ -50,8 +50,8 @@ public: } bool shouldDumpWrappedObjects() const override { return true; } - void setMemoryBacking(const char*, const char*, const char*) override { } - void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override { } + void setMemoryBacking(const char*, const char*, const char*) override {} + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} private: struct TraceValue { diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 270527d551a9..8092b1d10659 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,39 @@ 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, this->getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props)); SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds, - surface); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); layerUpdateQueue->clear(); // Draw visual debugging features @@ -122,77 +138,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() { @@ -201,21 +156,41 @@ void SkiaOpenGLPipeline::onStop() { } } -bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, - ColorMode colorMode) { +static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) { + int query_value; + int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); + if (err != 0 || query_value < 0) { + ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value); + return; + } + auto min_undequeued_buffers = static_cast<uint32_t>(query_value); + + int bufferCount = min_undequeued_buffers + 2 + extraBuffers; + native_window_set_buffer_count(window, bufferCount); +} + +bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, + ColorMode colorMode, uint32_t extraBuffers) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } + setSurfaceColorProperties(colorMode); + if (surface) { - const bool wideColorGamut = colorMode == ColorMode::WideColorGamut; - mEglSurface = mEglManager.createSurface(surface, wideColorGamut); + mRenderThread.requireGlContext(); + auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); + if (!newSurface) { + return false; + } + mEglSurface = newSurface.unwrap(); } if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); + setBufferCount(surface, extraBuffers); return true; } @@ -244,203 +219,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..3fe0f92b1924 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,32 +27,32 @@ 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; + GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } 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; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + renderthread::ColorMode colorMode, uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; 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..1f9ab5a242b4 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -17,6 +17,7 @@ #include "SkiaPipeline.h" #include <SkImageEncoder.h> +#include <SkImageInfo.h> #include <SkImagePriv.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> @@ -24,6 +25,7 @@ #include <SkPictureRecorder.h> #include "TreeInfo.h" #include "VectorDrawable.h" +#include "thread/CommonPool.h" #include "utils/TraceUtils.h" #include <unistd.h> @@ -48,10 +50,6 @@ SkiaPipeline::~SkiaPipeline() { unpinImages(); } -TaskManager* SkiaPipeline::getTaskManager() { - return mRenderThread.cacheManager().getTaskManager(); -} - void SkiaPipeline::onDestroyHardwareResources() { unpinImages(); mRenderThread.cacheManager().trimStaleResources(); @@ -81,18 +79,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 +100,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())); @@ -112,7 +108,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, const Rect& layerDamage = layers.entries()[i].damage; - SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface()); + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); int saveCount = layerCanvas->save(); SkASSERT(saveCount == 1); @@ -140,8 +136,6 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, layerCanvas->restoreToCount(saveCount); mLightCenter = savedLightCenter; - endCapture(layerNode->getLayerSurface()); - // cache the current context so that we can defer flushing it until // either all the layers have been rendered or the context changes GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext(); @@ -162,7 +156,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,34 +164,31 @@ 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, getSurfaceColorSpace()); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - SkBudgeted::kYes, info, 0, &props)); + SkBudgeted::kYes, info, 0, + this->getSurfaceOrigin(), &props)); if (node->getLayerSurface()) { // update the transform in window of the layer to reset its origin wrt light source // position Matrix4 windowTransform; damageAccumulator.computeCurrentTransform(&windowTransform); - node->getSkiaLayer()->inverseTransformInWindow = windowTransform; + node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform); } else { String8 cachesOutput; mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput, - &mRenderThread.renderState()); + &mRenderThread.renderState()); ALOGE("%s", cachesOutput.string()); if (errorHandler) { std::ostringstream err; err << "Unable to create layer for " << node->getName(); const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); err << ", size " << info.width() << "x" << info.height() << " max size " - << maxTextureSize << " color type " << (int)info.colorType() - << " has context " << (int)(mRenderThread.getGrContext() != nullptr); + << maxTextureSize << " color type " << (int)info.colorType() << " has context " + << (int)(mRenderThread.getGrContext() != nullptr); errorHandler->onError(err.str()); } } @@ -206,16 +197,11 @@ 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) { ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); - sk_sp<SkColorFilter> colorFilter; - auto image = bitmap->makeImage(&colorFilter); + auto image = bitmap->makeImage(); if (image.get() && !bitmap->isHardware()) { SkImage_pinAsTexture(image.get(), context); SkImage_unpinAsTexture(image.get(), context); @@ -236,85 +222,68 @@ void SkiaPipeline::renderVectorDrawableCache() { } } -class SkiaPipeline::SavePictureProcessor : public TaskProcessor<bool> { -public: - explicit SavePictureProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {} - - struct SavePictureTask : public Task<bool> { - sk_sp<SkData> data; - std::string filename; - }; - - void savePicture(const sk_sp<SkData>& data, const std::string& filename) { - sp<SavePictureTask> task(new SavePictureTask()); - task->data = data; - task->filename = filename; - TaskProcessor<bool>::add(task); - } - - virtual void onProcess(const sp<Task<bool>>& task) override { - SavePictureTask* t = static_cast<SavePictureTask*>(task.get()); - - if (0 == access(t->filename.c_str(), F_OK)) { - task->setResult(false); +static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) { + CommonPool::post([data, filename] { + if (0 == access(filename.c_str(), F_OK)) { return; } - SkFILEWStream stream(t->filename.c_str()); + SkFILEWStream stream(filename.c_str()); if (stream.isValid()) { - stream.write(t->data->data(), t->data->size()); + stream.write(data->data(), data->size()); stream.flush(); SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), - t->filename.c_str()); + filename.c_str()); } - - task->setResult(true); - } -}; + }); +} SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { - bool recordingPicture = mCaptureSequence > 0; char prop[PROPERTY_VALUE_MAX] = {'\0'}; - if (!recordingPicture) { + if (mCaptureSequence <= 0) { property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0"); - recordingPicture = prop[0] != '0' && - mCapturedFile != prop; // ensure we capture only once per filename - if (recordingPicture) { + if (prop[0] != '0' && mCapturedFile != prop) { mCapturedFile = prop; mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1); } } - if (recordingPicture) { + if (mCaptureSequence > 0 || mPictureCapturedCallback) { mRecorder.reset(new SkPictureRecorder()); - return mRecorder->beginRecording(surface->width(), surface->height(), nullptr, - SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + SkCanvas* pictureCanvas = + mRecorder->beginRecording(surface->width(), surface->height(), nullptr, + SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height()); + mNwayCanvas->addCanvas(surface->getCanvas()); + mNwayCanvas->addCanvas(pictureCanvas); + return mNwayCanvas.get(); } } return surface->getCanvas(); } void SkiaPipeline::endCapture(SkSurface* surface) { + mNwayCanvas.reset(); if (CC_UNLIKELY(mRecorder.get())) { + ATRACE_CALL(); sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); - surface->getCanvas()->drawPicture(picture); if (picture->approximateOpCount() > 0) { - auto data = picture->serialize(); - - // offload saving to file in a different thread - if (!mSavePictureProcessor.get()) { - TaskManager* taskManager = getTaskManager(); - mSavePictureProcessor = new SavePictureProcessor( - taskManager->canRunTasks() ? taskManager : nullptr); + if (mCaptureSequence > 0) { + ATRACE_BEGIN("picture->serialize"); + auto data = picture->serialize(); + ATRACE_END(); + + // offload saving to file in a different thread + if (1 == mCaptureSequence) { + savePictureAsync(data, mCapturedFile); + } else { + savePictureAsync(data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); + } + mCaptureSequence--; } - if (1 == mCaptureSequence) { - mSavePictureProcessor->savePicture(data, mCapturedFile); - } else { - mSavePictureProcessor->savePicture( - data, - mCapturedFile + "_" + std::to_string(mCaptureSequence)); + if (mPictureCapturedCallback) { + std::invoke(mPictureCapturedCallback, std::move(picture)); } - mCaptureSequence--; } mRecorder.reset(); } @@ -322,28 +291,35 @@ 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, + const SkMatrix& preTransform) { + bool previousSkpEnabled = Properties::skpCaptureEnabled; + if (mPictureCapturedCallback) { + Properties::skpCaptureEnabled = true; + } + 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, preTransform); endCapture(surface.get()); if (CC_UNLIKELY(Properties::debugOverdraw)) { - renderOverdraw(layers, clip, nodes, contentDrawBounds, surface); + renderOverdraw(layers, clip, nodes, contentDrawBounds, surface, preTransform); } ATRACE_NAME("flush commands"); surface->getCanvas()->flush(); + + Properties::skpCaptureEnabled = previousSkpEnabled; } namespace { @@ -351,17 +327,18 @@ static Rect nodeBounds(RenderNode& node) { auto& props = node.properties(); return Rect(props.getLeft(), props.getTop(), props.getRight(), props.getBottom()); } -} +} // namespace 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, + const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); - canvas->androidFramework_setDeviceClipRestriction(clip.roundOut()); + canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut()); + canvas->concat(preTransform); // 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); } @@ -466,6 +443,18 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } +void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { + if (colorMode == ColorMode::SRGB) { + mSurfaceColorType = SkColorType::kN32_SkColorType; + mSurfaceColorSpace = SkColorSpace::MakeSRGB(); + } else if (colorMode == ColorMode::WideColorGamut) { + mSurfaceColorType = DeviceInfo::get()->getWideColorType(); + mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported color mode."); + } +} + // Overdraw debugging // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences. @@ -475,16 +464,27 @@ void SkiaPipeline::dumpResourceCacheUsage() const { // (3) Requires RGBA colors (instead of BGRA). static const uint32_t kOverdrawColors[2][6] = { { - 0x00000000, 0x00000000, 0x2f2f0000, 0x2f002f00, 0x3f00003f, 0x7f00007f, + 0x00000000, + 0x00000000, + 0x2f2f0000, + 0x2f002f00, + 0x3f00003f, + 0x7f00007f, }, { - 0x00000000, 0x00000000, 0x2f2f0000, 0x4f004f4f, 0x5f50335f, 0x7f00007f, + 0x00000000, + 0x00000000, + 0x2f2f0000, + 0x4f004f4f, + 0x5f50335f, + 0x7f00007f, }, }; void SkiaPipeline::renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, - const Rect& contentDrawBounds, sk_sp<SkSurface> surface) { + const Rect& contentDrawBounds, sk_sp<SkSurface> surface, + const SkMatrix& preTransform) { // Set up the overdraw canvas. SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height()); sk_sp<SkSurface> offscreen = surface->makeSurface(offscreenInfo); @@ -494,7 +494,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, preTransform); 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 e9fba3a56cab..41d864653b67 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" @@ -33,8 +33,6 @@ public: explicit SkiaPipeline(renderthread::RenderThread& thread); virtual ~SkiaPipeline(); - TaskManager* getTaskManager() override; - void onDestroyHardwareResources() override; bool pinImages(std::vector<SkImage*>& mutableImages) override; @@ -42,24 +40,25 @@ 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 override { 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 Rect& contentDrawBounds, sk_sp<SkSurface> surface); + const std::vector<sp<RenderNode>>& nodes, bool opaque, + const Rect& contentDrawBounds, sk_sp<SkSurface> surface, + const SkMatrix& preTransform); 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,23 +96,31 @@ 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; mLightCenter = lightGeometry.center; } + void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) override { + mPictureCapturedCallback = callback; + } + protected: void dumpResourceCacheUsage() const; + void setSurfaceColorProperties(renderthread::ColorMode colorMode); 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 Rect& contentDrawBounds, SkCanvas* canvas); + const std::vector<sp<RenderNode>>& nodes, bool opaque, + const Rect& contentDrawBounds, SkCanvas* canvas, + const SkMatrix& preTransform); /** * Debugging feature. Draws a semi-transparent overlay on each pixel, indicating @@ -121,7 +128,7 @@ private: */ void renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, const Rect& contentDrawBounds, - sk_sp<SkSurface>); + sk_sp<SkSurface> surface, const SkMatrix& preTransform); /** * Render mVectorDrawables into offscreen buffers. @@ -148,16 +155,14 @@ private: * mCaptureSequence counts how many frames are left to take in the sequence. */ int mCaptureSequence = 0; - /** - * mSavePictureProcessor is used to run the file saving code in a separate thread. - */ - class SavePictureProcessor; - sp<SavePictureProcessor> mSavePictureProcessor; + /** * mRecorder holds the current picture recorder. We could store it on the stack to support * parallel tryCapture calls (not really needed). */ std::unique_ptr<SkPictureRecorder> mRecorder; + std::unique_ptr<SkNWayCanvas> mNwayCanvas; + std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; static float mLightRadius; static uint8_t mAmbientShadowAlpha; diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h index 3233c8d4ea7e..dc8420f4e01b 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..16c8b8923074 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -17,11 +17,15 @@ #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/VkFunctorDrawable.h" +#include "pipeline/skia/VkInteropFunctorDrawable.h" namespace android { namespace uirenderer { @@ -78,6 +82,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 +95,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); } } @@ -105,55 +113,81 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared. mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier); auto& renderNodeDrawable = mDisplayList->mChildNodes.back(); + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + // Put Vulkan WebViews with non-rectangular clips in a HW layer + renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex()); + } drawDrawable(&renderNodeDrawable); // 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()); + FunctorDrawable* functorDrawable; + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>( + functor, listener, asSkCanvas()); + } else { + functorDrawable = + mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, listener, asSkCanvas()); + } + mDisplayList->mChildFunctors.push_back(functorDrawable); + drawDrawable(functorDrawable); } -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); +void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { + FunctorDrawable* functorDrawable; + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + functorDrawable = mDisplayList->allocateDrawable<VkFunctorDrawable>(functor, asSkCanvas()); + } else { + functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, asSkCanvas()); } - -private: - sp<VectorDrawableRoot> mRoot; - SkRect mBounds; -}; + mDisplayList->mChildFunctors.push_back(functorDrawable); + drawDrawable(functorDrawable); +} void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { - drawDrawable(mDisplayList->allocateDrawable<VectorDrawable>(tree)); - mDisplayList->mVectorDrawables.push_back(tree); + mRecorder.drawVectorDrawable(tree); + SkMatrix mat; + this->getMatrix(&mat); + mDisplayList->appendVD(tree, mat); } // ---------------------------------------------------------------------------- // Recording Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- +SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint) { + 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) { + SkPaint& tmpPaint = paint.writeable(); + + if (fixBlending) { + tmpPaint.setBlendMode(SkBlendMode::kDstOut); + } + + // 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)); + sk_sp<SkImage> image = bitmap.makeImage(); + mRecorder.drawImage(image, left, top, filterBitmap(paint), 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 +200,8 @@ 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)); + sk_sp<SkImage> image = bitmap.makeImage(); + mRecorder.drawImage(image, 0, 0, filterBitmap(paint), bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique()) { mDisplayList->mMutableImages.push_back(image.get()); } @@ -181,11 +213,9 @@ 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); + sk_sp<SkImage> image = bitmap.makeImage(); + mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint), + SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() && !dstRect.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); @@ -216,23 +246,14 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch lattice.fBounds = nullptr; SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - SkPaint tmpPaint; - 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); + 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); } - - mRecorder.drawImageLattice(image.get(), lattice, dst, filteredPaint); + sk_sp<SkImage> image = bitmap.makeImage(); + mRecorder.drawImageLattice(image, lattice, dst, filterBitmap(std::move(filteredPaint)), + bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); } @@ -244,6 +265,6 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma return 0; } -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 93807a5476e6..c42cea33211e 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" @@ -74,9 +74,10 @@ public: virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override; virtual void callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) override; + void drawWebViewFunctor(int functor) override; private: - SkLiteRecorder mRecorder; + RecordingCanvas mRecorder; std::unique_ptr<SkiaDisplayList> mDisplayList; StartReorderBarrierDrawable* mCurrentBarrier; @@ -89,46 +90,9 @@ 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); }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/OpDumper.h b/libs/hwui/pipeline/skia/SkiaUtils.h index edbe381fcc72..fa7f1fe2f746 100644 --- a/libs/hwui/OpDumper.h +++ b/libs/hwui/pipeline/skia/SkiaUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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,18 +16,13 @@ #pragma once -#include <ostream> +#include <SkRect.h> namespace android { -namespace uirenderer { -struct RecordedOp; - -class OpDumper { -public: - static void dump(const RecordedOp& op, std::ostream& output, int level = 0); - static const char* opName(const RecordedOp& op); +static inline SkRect SkRectMakeLargest() { + const SkScalar v = SK_ScalarMax; + return {-v, -v, v, v}; }; -}; // namespace uirenderer -}; // namespace android +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 5825060f902a..e8cb219db320 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -18,9 +18,10 @@ #include "DeferredLayerUpdater.h" #include "Readback.h" +#include "ShaderCache.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" -#include "VkLayer.h" +#include "VkInteropFunctorDrawable.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" @@ -41,40 +42,37 @@ namespace uirenderer { namespace skiapipeline { SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) - : SkiaPipeline(thread), mVkManager(thread.vulkanManager()) {} + : SkiaPipeline(thread), mVkManager(thread.vulkanManager()) { + thread.renderState().registerContextCallback(this); +} + +SkiaVulkanPipeline::~SkiaVulkanPipeline() { + mRenderThread.renderState().removeContextCallback(this); +} MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { return MakeCurrentResult::AlreadyCurrent; } Frame SkiaVulkanPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, - "drawRenderNode called on a context with no surface!"); - - SkSurface* backBuffer = mVkManager.getBackbufferSurface(mVkSurface); - if (backBuffer == nullptr) { - SkDebugf("failed to get backbuffer"); - return Frame(-1, -1, 0); - } - - Frame frame(backBuffer->width(), backBuffer->height(), mVkManager.getAge(mVkSurface)); - return frame; + LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!"); + return mVkManager.dequeueNextBuffer(mVkSurface); } 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(); + sk_sp<SkSurface> backBuffer = mVkSurface->getCurrentSkSurface(); if (backBuffer.get() == nullptr) { return false; } SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds, - backBuffer); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, + mVkSurface->getCurrentPreTransform()); + ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext()); layerUpdateQueue->clear(); // Draw visual debugging features @@ -103,41 +101,33 @@ bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect currentFrameInfo->markSwapBuffers(); if (*requireSwap) { - mVkManager.swapBuffers(mVkSurface); + mVkManager.swapBuffers(mVkSurface, screenDirty); } 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(); + mRenderThread.requireVkContext(); - return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::Vulkan); + return new DeferredLayerUpdater(mRenderThread.renderState()); } void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, - ColorMode colorMode) { +bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, + ColorMode colorMode, uint32_t extraBuffers) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); mVkSurface = nullptr; } + setSurfaceColorProperties(colorMode); if (surface) { - // TODO: handle color mode - mVkSurface = mVkManager.createSurface(surface); + mRenderThread.requireVkContext(); + mVkSurface = + mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, mSurfaceColorType, + mRenderThread.getGrContext(), extraBuffers); } return mVkSurface != nullptr; @@ -152,27 +142,20 @@ 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, SkBitmap& skBitmap) { - // TODO: implement this function for Vulkan pipeline - // code below is a hack to avoid crashing because of missing HW Bitmap support - sp<GraphicBuffer> buffer = new GraphicBuffer( - skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, - std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") + - std::to_string(getpid()) + "]"); - status_t error = buffer->initCheck(); - if (error < 0) { - ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()"); - return nullptr; + LOG_ALWAYS_FATAL("Unimplemented"); + return nullptr; +} + +void SkiaVulkanPipeline::onContextDestroyed() { + if (mVkSurface) { + mVkManager.destroySurface(mVkSurface); + mVkSurface = nullptr; } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), skBitmap.info())); } } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 3d00386bf5cd..31734783de7f 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -18,30 +18,32 @@ #include "SkiaPipeline.h" #include "renderthread/VulkanManager.h" +#include "renderthread/VulkanSurface.h" + +#include "renderstate/RenderState.h" namespace android { namespace uirenderer { namespace skiapipeline { -class SkiaVulkanPipeline : public SkiaPipeline { +class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback { public: explicit SkiaVulkanPipeline(renderthread::RenderThread& thread); - virtual ~SkiaVulkanPipeline() {} + virtual ~SkiaVulkanPipeline(); 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; + GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } 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; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, + renderthread::ColorMode colorMode, uint32_t extraBuffers) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; @@ -50,6 +52,9 @@ public: static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread, SkBitmap& skBitmap); +protected: + void onContextDestroyed() override; + private: renderthread::VulkanManager& mVkManager; renderthread::VulkanSurface* mVkSurface = nullptr; 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/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp index 8fb621d24866..e783f389feb8 100644 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp +++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp @@ -262,12 +262,7 @@ void VectorDrawableAtlas::delayedReleaseEntries() { } sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - sk_sp<SkColorSpace> colorSpace = nullptr; -#else - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); -#endif - SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); // This must have a top-left origin so that calls to surface->canvas->writePixels // performs a basic texture upload instead of a more complex drawing operation return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, kTopLeft_GrSurfaceOrigin, diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp new file mode 100644 index 000000000000..112792611fc3 --- /dev/null +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -0,0 +1,133 @@ +/* + * 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 "VkFunctorDrawable.h" +#include <private/hwui/DrawVkInfo.h> + +#include <GrBackendDrawableInfo.h> +#include <SkAndroidFrameworkUtils.h> +#include <SkImage.h> +#include <utils/Color.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <vk/GrVkTypes.h> +#include <thread> +#include "renderthread/RenderThread.h" +#include "renderthread/VulkanManager.h" +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +VkFunctorDrawHandler::VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle, + const SkMatrix& matrix, const SkIRect& clip, + const SkImageInfo& image_info) + : INHERITED() + , mFunctorHandle(functor_handle) + , mMatrix(matrix) + , mClip(clip) + , mImageInfo(image_info) {} + +VkFunctorDrawHandler::~VkFunctorDrawHandler() { + if (mDrawn) { + mFunctorHandle->postDrawVk(); + } +} + +void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { + ATRACE_CALL(); + if (!renderthread::RenderThread::isCurrent()) + LOG_ALWAYS_FATAL("VkFunctorDrawHandler::draw not called on render thread"); + + GrVkDrawableInfo vulkan_info; + if (!info.getVkDrawableInfo(&vulkan_info)) { + return; + } + renderthread::VulkanManager& vk_manager = + renderthread::RenderThread::getInstance().vulkanManager(); + mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams()); + + SkMatrix44 mat4(mMatrix); + VkFunctorDrawParams params{ + .width = mImageInfo.width(), + .height = mImageInfo.height(), + .color_space_ptr = mImageInfo.colorSpace(), + .clip_left = mClip.fLeft, + .clip_top = mClip.fTop, + .clip_right = mClip.fRight, + .clip_bottom = mClip.fBottom, + }; + mat4.asColMajorf(¶ms.transform[0]); + params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer; + params.color_attachment_index = vulkan_info.fColorAttachmentIndex; + params.compatible_render_pass = vulkan_info.fCompatibleRenderPass; + params.format = vulkan_info.fFormat; + + mFunctorHandle->drawVk(params); + mDrawn = true; + + vulkan_info.fDrawBounds->offset.x = mClip.fLeft; + vulkan_info.fDrawBounds->offset.y = mClip.fTop; + vulkan_info.fDrawBounds->extent.width = mClip.fRight - mClip.fLeft; + vulkan_info.fDrawBounds->extent.height = mClip.fBottom - mClip.fTop; +} + +VkFunctorDrawable::~VkFunctorDrawable() {} + +void VkFunctorDrawable::onDraw(SkCanvas* canvas) { + // "canvas" is either SkNWayCanvas created by SkiaPipeline::tryCapture (SKP capture use case) or + // AlphaFilterCanvas (used by RenderNodeDrawable to apply alpha in certain cases). + // "VkFunctorDrawable::onDraw" is not invoked for the most common case, when drawing in a GPU + // canvas. + + if (canvas->getGrContext() == nullptr) { + // We're dumping a picture, render a light-blue rectangle instead + SkPaint paint; + paint.setColor(0xFF81D4FA); + canvas->drawRect(mBounds, paint); + } else { + // Handle the case when "canvas" is AlphaFilterCanvas. Find the wrapped GPU canvas. + SkCanvas* gpuCanvas = SkAndroidFrameworkUtils::getBaseWrappedCanvas(canvas); + // Enforce "canvas" must be an AlphaFilterCanvas. For GPU canvas, the call should come from + // onSnapGpuDrawHandler. + LOG_ALWAYS_FATAL_IF(gpuCanvas == canvas, + "VkFunctorDrawable::onDraw() should not be called with a GPU canvas!"); + + // This will invoke onSnapGpuDrawHandler and regular draw flow. + gpuCanvas->drawDrawable(this); + } +} + +std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler( + GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip, + const SkImageInfo& image_info) { + if (backendApi != GrBackendApi::kVulkan) { + return nullptr; + } + std::unique_ptr<VkFunctorDrawHandler> draw; + if (mAnyFunctor.index() == 0) { + return std::make_unique<VkFunctorDrawHandler>(std::get<0>(mAnyFunctor).handle, matrix, clip, + image_info); + } else { + LOG_ALWAYS_FATAL("Not implemented"); + } +} + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.h b/libs/hwui/pipeline/skia/VkFunctorDrawable.h new file mode 100644 index 000000000000..d3f97773b91d --- /dev/null +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.h @@ -0,0 +1,71 @@ +/* + * 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 <SkImageInfo.h> +#include <ui/GraphicBuffer.h> +#include <utils/RefBase.h> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +/** + * This draw handler will be returned by VkFunctorDrawable's onSnapGpuDrawHandler. It allows us to + * issue Vulkan commands while the command buffer is being flushed. + */ +class VkFunctorDrawHandler : public FunctorDrawable::GpuDrawHandler { +public: + VkFunctorDrawHandler(sp<WebViewFunctor::Handle> functor_handle, const SkMatrix& matrix, + const SkIRect& clip, const SkImageInfo& image_info); + ~VkFunctorDrawHandler() override; + + void draw(const GrBackendDrawableInfo& info) override; + +private: + typedef GpuDrawHandler INHERITED; + sp<WebViewFunctor::Handle> mFunctorHandle; + const SkMatrix mMatrix; + const SkIRect mClip; + const SkImageInfo mImageInfo; + + bool mDrawn = false; +}; + +/** + * This drawable wraps a Vulkan functor enabling it to be recorded into a list of Skia drawing + * commands. + */ +class VkFunctorDrawable : public FunctorDrawable { +public: + using FunctorDrawable::FunctorDrawable; + + ~VkFunctorDrawable() override; + +protected: + // SkDrawable functions: + void onDraw(SkCanvas* canvas) override; + std::unique_ptr<FunctorDrawable::GpuDrawHandler> onSnapGpuDrawHandler( + GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clip, + const SkImageInfo& image_info) override; +}; + +} // 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..706325f00bd2 --- /dev/null +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -0,0 +1,205 @@ +/* + * 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 <utils/Color.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <thread> +#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 <utils/GLUtils.h> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +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() { + if (!sEglManager.hasEglContext()) { + sEglManager.initialize(); + } + } +}; + +void VkInteropFunctorDrawable::vkInvokeFunctor(Functor* functor) { + ScopedDrawRequest _drawRequest{}; + 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. + { + 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; + } + + 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]); + info.color_space_ptr = canvas->imageInfo().colorSpace(); + + 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; + } + + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if (mAnyFunctor.index() == 0) { + std::get<0>(mAnyFunctor).handle->drawGl(info); + } else { + (*(std::get<1>(mAnyFunctor).functor))(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); + } + + 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, + canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin); + canvas->drawImage(functorImage, 0, 0, &paint); + canvas->restore(); +} + +VkInteropFunctorDrawable::~VkInteropFunctorDrawable() { + if (auto lp = std::get_if<LegacyFunctor>(&mAnyFunctor)) { + if (lp->listener) { + ScopedDrawRequest _drawRequest{}; + lp->listener->onGlFunctorReleased(lp->functor); + } + } +} + +void VkInteropFunctorDrawable::syncFunctor(const WebViewSyncData& data) const { + ScopedDrawRequest _drawRequest{}; + FunctorDrawable::syncFunctor(data); +} + +} // 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..c47ee114263f --- /dev/null +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h @@ -0,0 +1,54 @@ +/* + * 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 <ui/GraphicBuffer.h> +#include <utils/RefBase.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: + using FunctorDrawable::FunctorDrawable; + + virtual ~VkInteropFunctorDrawable(); + + static void vkInvokeFunctor(Functor* functor); + + void syncFunctor(const WebViewSyncData& data) const override; + +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/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index efa9da27199d..501b8df9bc36 100644 --- a/libs/hwui/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_DRAW_GL_INFO_H #define ANDROID_HWUI_DRAW_GL_INFO_H +#include <SkColorSpace.h> + namespace android { namespace uirenderer { @@ -41,6 +43,9 @@ struct DrawGlInfo { // Input: current transform matrix, in OpenGL format float transform[16]; + // Input: Color space. + const SkColorSpace* color_space_ptr; + // Output: dirty region to redraw float dirtyLeft; float dirtyTop; @@ -83,7 +88,7 @@ struct DrawGlInfo { }; }; // struct DrawGlInfo -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_DRAW_GL_INFO_H diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h new file mode 100644 index 000000000000..4ae0f5a0a2e5 --- /dev/null +++ b/libs/hwui/private/hwui/DrawVkInfo.h @@ -0,0 +1,76 @@ +/* + * 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_HWUI_DRAW_VK_INFO_H +#define ANDROID_HWUI_DRAW_VK_INFO_H + +#include <SkColorSpace.h> +#include <vulkan/vulkan.h> + +namespace android { +namespace uirenderer { + +struct VkFunctorInitParams { + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue queue; + uint32_t graphics_queue_index; + uint32_t api_version; + const char* const* enabled_instance_extension_names; + uint32_t enabled_instance_extension_names_length; + const char* const* enabled_device_extension_names; + uint32_t enabled_device_extension_names_length; + const VkPhysicalDeviceFeatures2* device_features_2; +}; + +struct VkFunctorDrawParams { + // Input: current width/height of destination surface. + int width; + int height; + + // Input: current transform matrix + float transform[16]; + + // Input WebView should do its main compositing draws into this. It cannot do + // anything that would require stopping the render pass. + VkCommandBuffer secondary_command_buffer; + + // Input: The main color attachment index where secondary_command_buffer will + // eventually be submitted. + uint32_t color_attachment_index; + + // Input: A render pass which will be compatible to the one which the + // secondary_command_buffer will be submitted into. + VkRenderPass compatible_render_pass; + + // Input: Format of the destination surface. + VkFormat format; + + // Input: Color space. + const SkColorSpace* color_space_ptr; + + // Input: current clip rect + int clip_left; + int clip_top; + int clip_right; + int clip_bottom; +}; + +} // namespace uirenderer +} // namespace android + +#endif // ANDROID_HWUI_DRAW_VK_INFO_H diff --git a/libs/hwui/private/hwui/WebViewFunctor.h b/libs/hwui/private/hwui/WebViewFunctor.h new file mode 100644 index 000000000000..96da947ace08 --- /dev/null +++ b/libs/hwui/private/hwui/WebViewFunctor.h @@ -0,0 +1,78 @@ +/* + * 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 FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H +#define FRAMEWORKS_BASE_WEBVIEWFUNCTOR_H + +#include <cutils/compiler.h> +#include <private/hwui/DrawGlInfo.h> +#include <private/hwui/DrawVkInfo.h> + +namespace android::uirenderer { + +enum class RenderMode { + OpenGL_ES, + Vulkan, +}; + +// Static for the lifetime of the process +ANDROID_API RenderMode WebViewFunctor_queryPlatformRenderMode(); + +struct WebViewSyncData { + bool applyForceDark; +}; + +struct WebViewFunctorCallbacks { + // kModeSync, called on RenderThread + void (*onSync)(int functor, void* data, const WebViewSyncData& syncData); + + // Called when either the context is destroyed _or_ when the functor's last reference goes + // away. Will always be called with an active context and always on renderthread. + void (*onContextDestroyed)(int functor, void* data); + + // Called when the last reference to the handle goes away and the handle is considered + // irrevocably destroyed. Will always be proceeded by a call to onContextDestroyed if + // this functor had ever been drawn. + void (*onDestroyed)(int functor, void* data); + + union { + struct { + // Called on RenderThread. initialize is guaranteed to happen before this call + void (*draw)(int functor, void* data, const DrawGlInfo& params); + } gles; + struct { + // Called either the first time the functor is used or the first time it's used after + // a call to onContextDestroyed. + void (*initialize)(int functor, void* data, const VkFunctorInitParams& params); + void (*draw)(int functor, void* data, const VkFunctorDrawParams& params); + void (*postDraw)(int functor, void*); + } vk; + }; +}; + +// Creates a new WebViewFunctor from the given prototype. The prototype is copied after +// this function returns. Caller retains full ownership of it. +// Returns -1 if the creation fails (such as an unsupported functorMode + platform mode combination) +ANDROID_API int WebViewFunctor_create(void* data, const WebViewFunctorCallbacks& prototype, RenderMode functorMode); + +// May be called on any thread to signal that the functor should be destroyed. +// The functor will receive an onDestroyed when the last usage of it is released, +// and it should be considered alive & active until that point. +ANDROID_API void WebViewFunctor_release(int functor); + +} // namespace android::uirenderer + +#endif // FRAMEWORKS_BASE_WEBVIEWFUNCTOR_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..1b638c12ac7b 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -22,10 +22,12 @@ #include "pipeline/skia/ShaderCache.h" #include "pipeline/skia/SkiaMemoryTracer.h" #include "renderstate/RenderState.h" +#include "thread/CommonPool.h" #include <GrContextOptions.h> #include <SkExecutor.h> #include <SkGraphics.h> +#include <SkMathPriv.h> #include <gui/Surface.h> #include <math.h> #include <set> @@ -41,18 +43,24 @@ namespace renderthread { #define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f) #define BACKGROUND_RETENTION_PERCENTAGE (0.5f) -// for super large fonts we will draw them as paths so no need to keep linearly -// increasing the font cache size. -#define FONT_CACHE_MIN_MB (0.5f) -#define FONT_CACHE_MAX_MB (4.0f) +CacheManager::CacheManager(const DisplayInfo& display) + : mMaxSurfaceArea(display.w * display.h) + , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER) + , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE) + // This sets the maximum size for a single texture atlas in the GPU font cache. If + // necessary, the cache can allocate additional textures that are counted against the + // total cache limits provided to Skia. + , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea)) + // This sets the maximum size of the CPU font cache to be at least the same size as the + // total number of GPU font caches (i.e. 4 separate GPU atlases). + , mMaxCpuFontCacheBytes(std::max(mMaxGpuFontAtlasBytes*4, SkGraphics::GetFontCacheLimit())) + , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) { + + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); -CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display.w * display.h) { mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas( mMaxSurfaceArea / 2, skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface); - if (Properties::isSkiaEnabled()) { - skiapipeline::ShaderCache::get().initShaderDiskCache(); - } } void CacheManager::reset(sk_sp<GrContext> context) { @@ -63,7 +71,7 @@ void CacheManager::reset(sk_sp<GrContext> context) { if (context) { mGrContext = std::move(context); mGrContext->getResourceCacheLimits(&mMaxResources, nullptr); - updateContextCacheSizes(); + mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes); } } @@ -75,67 +83,23 @@ void CacheManager::destroy() { skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface); } -void CacheManager::updateContextCacheSizes() { - mMaxResourceBytes = mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER; - mBackgroundResourceBytes = mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE; - - mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes); -} - -class CacheManager::SkiaTaskProcessor : public TaskProcessor<bool>, public SkExecutor { +class CommonPoolExecutor : public SkExecutor { public: - explicit SkiaTaskProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {} - - // This is really a Task<void> but that doesn't really work when Future<> - // expects to be able to get/set a value - struct SkiaTask : public Task<bool> { - std::function<void()> func; - }; - - virtual void add(std::function<void(void)> func) override { - sp<SkiaTask> task(new SkiaTask()); - task->func = func; - TaskProcessor<bool>::add(task); - } - - virtual void onProcess(const sp<Task<bool> >& task) override { - SkiaTask* t = static_cast<SkiaTask*>(task.get()); - t->func(); - task->setResult(true); - } + virtual void add(std::function<void(void)> func) override { CommonPool::post(std::move(func)); } }; -void CacheManager::configureContext(GrContextOptions* contextOptions) { - contextOptions->fAllowPathMaskCaching = true; - - float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f; - float fontCacheMB = 0; - float decimalVal = std::modf(screenMP, &fontCacheMB); - - // This is a basic heuristic to size the cache to a multiple of 512 KB - if (decimalVal > 0.8f) { - fontCacheMB += 1.0f; - } else if (decimalVal > 0.5f) { - fontCacheMB += 0.5f; - } - - // set limits on min/max size of the cache - fontCacheMB = std::max(FONT_CACHE_MIN_MB, std::min(FONT_CACHE_MAX_MB, fontCacheMB)); +static CommonPoolExecutor sDefaultExecutor; - // We must currently set the size of the text cache based on the size of the - // display even though we like to be dynamicallysizing it to the size of the window. - // Skia's implementation doesn't provide a mechanism to resize the font cache due to - // the potential cost of recreating the glyphs. - contextOptions->fGlyphCacheTextureMaximumBytes = fontCacheMB * 1024 * 1024; - - if (mTaskManager.canRunTasks()) { - if (!mTaskProcessor.get()) { - mTaskProcessor = new SkiaTaskProcessor(&mTaskManager); - } - contextOptions->fExecutor = mTaskProcessor.get(); - } +void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, + ssize_t size) { + contextOptions->fAllowPathMaskCaching = true; + contextOptions->fGlyphCacheTextureMaximumBytes = mMaxGpuFontAtlasBytes; + contextOptions->fExecutor = &sDefaultExecutor; - 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) { @@ -149,6 +113,7 @@ void CacheManager::trimMemory(TrimMemoryMode mode) { case TrimMemoryMode::Complete: mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(mMaxSurfaceArea / 2); mGrContext->freeGpuResources(); + SkGraphics::PurgeAllCaches(); break; case TrimMemoryMode::UiHidden: // Here we purge all the unlocked scratch resources and then toggle the resources cache @@ -157,8 +122,14 @@ void CacheManager::trimMemory(TrimMemoryMode mode) { mGrContext->purgeUnlockedResources(true); mGrContext->setResourceCacheLimits(mMaxResources, mBackgroundResourceBytes); mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes); + SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); break; } + + // We must sync the cpu to make sure deletions of resources still queued up on the GPU actually + // happen. + mGrContext->flush(kSyncCpu_GrFlushFlag, 0, nullptr); } void CacheManager::trimStaleResources() { @@ -215,11 +186,13 @@ 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 78d2539a733f..9a5a00fcf762 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -24,8 +24,6 @@ #include <vector> #include "pipeline/skia/VectorDrawableAtlas.h" -#include "thread/TaskManager.h" -#include "thread/TaskProcessor.h" namespace android { @@ -44,7 +42,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); @@ -54,8 +52,6 @@ public: size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } - TaskManager* getTaskManager() { return &mTaskManager; } - private: friend class RenderThread; @@ -63,14 +59,17 @@ private: void reset(sk_sp<GrContext> grContext); void destroy(); - void updateContextCacheSizes(); const size_t mMaxSurfaceArea; sk_sp<GrContext> mGrContext; int mMaxResources = 0; - size_t mMaxResourceBytes = 0; - size_t mBackgroundResourceBytes = 0; + const size_t mMaxResourceBytes; + const size_t mBackgroundResourceBytes; + + const size_t mMaxGpuFontAtlasBytes; + const size_t mMaxCpuFontCacheBytes; + const size_t mBackgroundCpuFontCacheBytes; struct PipelineProps { const void* pipelineKey = nullptr; @@ -78,10 +77,6 @@ private: }; sp<skiapipeline::VectorDrawableAtlas> mVectorDrawableAtlas; - - class SkiaTaskProcessor; - sp<SkiaTaskProcessor> mTaskProcessor; - TaskManager mTaskManager; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index bfa2ae7e72ff..f326ce8d23e9 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -17,27 +17,23 @@ #include "CanvasContext.h" #include <GpuMemoryTracker.h> +#include "../Properties.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 "thread/CommonPool.h" #include "utils/GLUtils.h" #include "utils/TimeUtils.h" -#include "../Properties.h" +#include "utils/TraceUtils.h" #include <cutils/properties.h> -#include <google/protobuf/io/zero_copy_stream_impl.h> #include <private/hwui/DrawGlInfo.h> #include <strings.h> @@ -45,14 +41,13 @@ #include <sys/stat.h> #include <algorithm> +#include <cstdint> #include <cstdlib> #include <functional> #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 +65,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 +78,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 +95,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, @@ -144,19 +105,18 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mGenerationID(0) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo()) - , mProfiler(mJankTracker.frames()) + , mJankTracker(&thread.globalProfileData(), DeviceInfo::get()->displayInfo()) + , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos()) , mContentDrawBounds(0, 0, 0, 0) , mRenderPipeline(std::move(renderPipeline)) { rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); - mRenderThread.renderState().registerCanvasContext(this); - mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); + mProfiler.setDensity(DeviceInfo::get()->displayInfo().density); + setRenderAheadDepth(Properties::defaultRenderAhead); } CanvasContext::~CanvasContext() { destroy(); - mRenderThread.renderState().unregisterCanvasContext(this); for (auto& node : mRenderNodes) { node->clearRoot(); } @@ -186,10 +146,24 @@ void CanvasContext::destroy() { void CanvasContext::setSurface(sp<Surface>&& surface) { ATRACE_CALL(); - mNativeSurface = std::move(surface); + if (surface) { + mNativeSurface = new ReliableSurface{std::move(surface)}; + mNativeSurface->setDequeueTimeout(500_ms); + } else { + mNativeSurface = nullptr; + } + + if (mRenderAheadDepth == 0 && DeviceInfo::get()->getMaxRefreshRate() > 66.6f) { + mFixedRenderAhead = false; + mRenderAheadCapacity = 1; + } else { + mFixedRenderAhead = true; + mRenderAheadCapacity = mRenderAheadDepth; + } - ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb; - bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode); + ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; + bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode, + mRenderAheadCapacity); mFrameNumber = -1; @@ -224,14 +198,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) { @@ -323,6 +303,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.damageAccumulator = &mDamageAccumulator; info.layerUpdateQueue = &mLayerUpdateQueue; + info.out.canDrawThisFrame = true; mAnimationContext->startFrame(info.mode); mRenderPipeline->onPrepareTree(); @@ -342,7 +323,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!mNativeSurface.get())) { + if (CC_UNLIKELY(!hasSurface())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -361,25 +342,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // the deadline for RT animations info.out.canDrawThisFrame = false; } - /* This logic exists to try and recover from a display latch miss, which essentially - * results in the bufferqueue being double-buffered instead of triple-buffered. - * SurfaceFlinger itself now tries to handle & recover from this situation, so this - * logic should no longer be necessary. As it's occasionally triggering when - * undesired disable it. - * TODO: Remove this entirely if the results are solid. - else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos() * 3 || - (latestVsync - mLastDropVsync) < 500_ms) { - // It's been several frame intervals, assume the buffer queue is fine - // or the last drop was too recent - info.out.canDrawThisFrame = true; - } else { - info.out.canDrawThisFrame = !isSwapChainStuffed(); - if (!info.out.canDrawThisFrame) { - // dropping frame - mLastDropVsync = mRenderThread.timeLord().latestVsync(); - } - } - */ } else { info.out.canDrawThisFrame = true; } @@ -390,7 +352,19 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.out.canDrawThisFrame = false; } - if (!info.out.canDrawThisFrame) { + if (info.out.canDrawThisFrame) { + int err = mNativeSurface->reserveNext(); + if (err != OK) { + mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); + info.out.canDrawThisFrame = false; + ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err)); + if (err != TIMED_OUT) { + // A timed out surface can still recover, but assume others are permanently dead. + setSurface(nullptr); + return; + } + } + } else { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); } @@ -436,25 +410,42 @@ void CanvasContext::notifyFramePending() { mRenderThread.pushBackFrameCallback(this); } +void CanvasContext::setPresentTime() { + int64_t presentTime = NATIVE_WINDOW_TIMESTAMP_AUTO; + int renderAhead = 0; + const auto frameIntervalNanos = mRenderThread.timeLord().frameIntervalNanos(); + if (mFixedRenderAhead) { + renderAhead = std::min(mRenderAheadDepth, mRenderAheadCapacity); + } else if (frameIntervalNanos < 15_ms) { + renderAhead = std::min(1, static_cast<int>(mRenderAheadCapacity)); + } + + if (renderAhead) { + presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) + + (frameIntervalNanos * (renderAhead + 1)); + } + native_window_set_buffers_timestamp(mNativeSurface.get(), presentTime); +} + void CanvasContext::draw() { SkRect dirty; mDamageAccumulator.finish(&dirty); - // TODO: Re-enable after figuring out cause of b/22592975 - // if (dirty.isEmpty() && Properties::skipEmptyFrames) { - // mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); - // return; - // } + if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) { + mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); + return; + } mCurrentFrameInfo->markIssueDrawCommandsStart(); Frame frame = mRenderPipeline->getFrame(); + setPresentTime(); SkRect windowDirty = computeDirtyRect(frame, &dirty); bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, - mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo, - mRenderNodes, &(profiler())); + mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, + &(profiler())); int64_t frameCompleteNr = mFrameCompleteCallbacks.size() ? getFrameNumber() : -1; @@ -531,13 +522,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 @@ -546,6 +530,17 @@ void CanvasContext::doFrame() { prepareAndDraw(nullptr); } +SkISize CanvasContext::getNextFrameSize() const { + ReliableSurface* surface = mNativeSurface.get(); + if (surface) { + SkISize size; + surface->query(NATIVE_WINDOW_WIDTH, &size.fWidth); + surface->query(NATIVE_WINDOW_HEIGHT, &size.fHeight); + return size; + } + return {INT32_MAX, INT32_MAX}; +} + void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); @@ -599,17 +594,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 +612,14 @@ 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.destroyRenderingContext(); + } else if (level >= TRIM_MEMORY_UI_HIDDEN) { + thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); } } @@ -673,68 +640,18 @@ 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(); for (auto& fence : mFrameFences) { - fence->getResult(); + fence.get(); } mFrameFences.clear(); } } -class CanvasContext::FuncTaskProcessor : public TaskProcessor<bool> { -public: - explicit FuncTaskProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {} - - virtual void onProcess(const sp<Task<bool> >& task) override { - FuncTask* t = static_cast<FuncTask*>(task.get()); - t->func(); - task->setResult(true); - } -}; - void CanvasContext::enqueueFrameWork(std::function<void()>&& func) { - if (!mFrameWorkProcessor.get()) { - mFrameWorkProcessor = new FuncTaskProcessor(mRenderPipeline->getTaskManager()); - } - sp<FuncTask> task(new FuncTask()); - task->func = func; - mFrameFences.push_back(task); - mFrameWorkProcessor->add(task); + mFrameFences.push_back(CommonPool::async(std::move(func))); } int64_t CanvasContext::getFrameNumber() { @@ -745,6 +662,27 @@ int64_t CanvasContext::getFrameNumber() { return mFrameNumber; } +bool CanvasContext::surfaceRequiresRedraw() { + if (!mNativeSurface) return false; + if (mHaveNewSurface) return true; + + int width = -1; + int height = -1; + ReliableSurface* surface = mNativeSurface.get(); + surface->query(NATIVE_WINDOW_WIDTH, &width); + surface->query(NATIVE_WINDOW_HEIGHT, &height); + + return width == mLastFrameWidth && height == mLastFrameHeight; +} + +void CanvasContext::setRenderAheadDepth(int renderAhead) { + if (renderAhead > 2 || renderAhead < 0 || mNativeSurface) { + return; + } + mFixedRenderAhead = true; + mRenderAheadDepth = static_cast<uint32_t>(renderAhead); +} + SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) { if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { // can't rely on prior content of window if viewport size changes diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 1c4e02d7df70..982c087b031a 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -16,30 +16,29 @@ #pragma once -#include "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" #include "DamageAccumulator.h" -#include "FrameBuilder.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" #include "FrameMetricsReporter.h" #include "IContextFactory.h" #include "IRenderPipeline.h" #include "LayerUpdateQueue.h" +#include "Lighting.h" +#include "ReliableSurface.h" #include "RenderNode.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" -#include "thread/Task.h" -#include "thread/TaskProcessor.h" #include <EGL/egl.h> #include <SkBitmap.h> #include <SkRect.h> +#include <SkSize.h> #include <cutils/compiler.h> #include <gui/Surface.h> #include <utils/Functor.h> #include <functional> +#include <future> #include <set> #include <string> #include <vector> @@ -77,8 +76,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 +97,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); @@ -121,10 +113,11 @@ public: void setSurface(sp<Surface>&& surface); bool pauseSurface(); void setStopped(bool stopped); - bool hasSurface() { return mNativeSurface.get(); } + bool hasSurface() const { 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 +130,6 @@ public: void prepareAndDraw(RenderNode* node); void buildLayer(RenderNode* node); - bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); void markLayerInUse(RenderNode* node); void destroyHardwareResources(); @@ -155,8 +147,6 @@ public: void setName(const std::string&& name); - void serializeDisplayListTree(); - void addRenderNode(RenderNode* node, bool placeFront); void removeRenderNode(RenderNode* node); @@ -194,6 +184,21 @@ public: mFrameCompleteCallbacks.push_back(std::move(func)); } + void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) { + mRenderPipeline->setPictureCapturedCallback(callback); + } + + void setForceDark(bool enable) { mUseForceDark = enable; } + + bool useForceDark() { + return mUseForceDark; + } + + // Must be called before setSurface + void setRenderAheadDepth(int renderAhead); + + SkISize getNextFrameSize() const; + private: CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline); @@ -206,6 +211,8 @@ private: void freePrefetchedLayers(); bool isSwapChainStuffed(); + bool surfaceRequiresRedraw(); + void setPresentTime(); SkRect computeDirtyRect(const Frame& frame, SkRect* dirty); @@ -213,7 +220,7 @@ private: EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; - sp<Surface> mNativeSurface; + sp<ReliableSurface> mNativeSurface; // stopped indicates the CanvasContext will reject actual redraw operations, // and defer repaint until it is un-stopped bool mStopped = false; @@ -224,6 +231,9 @@ private: // painted onto its surface. bool mIsDirty = false; SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default; + bool mFixedRenderAhead = false; + uint32_t mRenderAheadDepth = 0; + uint32_t mRenderAheadCapacity = 0; struct SwapHistory { SkRect damage; nsecs_t vsyncTime; @@ -240,8 +250,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; @@ -261,15 +272,7 @@ private: // Stores the bounds of the main content. Rect mContentDrawBounds; - // TODO: This is really a Task<void> but that doesn't really work - // when Future<> expects to be able to get/set a value - struct FuncTask : public Task<bool> { - std::function<void()> func; - }; - class FuncTaskProcessor; - - std::vector<sp<FuncTask>> mFrameFences; - sp<TaskProcessor<bool>> mFrameWorkProcessor; + std::vector<std::future<void>> mFrameFences; std::unique_ptr<IRenderPipeline> mRenderPipeline; std::vector<std::function<void(int64_t)>> mFrameCompleteCallbacks; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 51eeab7e46ce..91dc3bc6e603 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -109,9 +109,8 @@ void DrawFrameTask::run() { // Even if we aren't drawing this vsync pulse the next frame number will still be accurate if (CC_UNLIKELY(callback)) { - context->enqueueFrameWork([callback, frameNr = context->getFrameNumber()]() { - callback(frameNr); - }); + context->enqueueFrameWork( + [callback, frameNr = context->getFrameNumber()]() { callback(frameNr); }); } if (CC_LIKELY(canDrawThisFrame)) { 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..159cf497384a 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -16,27 +16,23 @@ #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 <gui/Surface.h> +#include <system/window.h> +#include <string> +#include <vector> #define GLES_VERSION 2 @@ -82,17 +78,25 @@ 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) {} + , mCurrentSurface(EGL_NO_SURFACE) + , mHasWideColorGamutSupport(false) {} + +EglManager::~EglManager() { + if (hasEglContext()) { + ALOGW("~EglManager() leaked an EGL context"); + } +} void EglManager::initialize() { if (hasEglContext()) return; @@ -107,7 +111,7 @@ void EglManager::initialize() { LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE, "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString()); - ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); + ALOGV("Initialized EGL, version %d.%d", (int)major, (int)minor); initExtensions(); @@ -126,26 +130,82 @@ 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); + + skcms_Matrix3x3 wideColorGamut; + LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut), + "Could not get gamut matrix from wideColorSpace"); + bool hasWideColorSpaceExtension = false; + if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) { + hasWideColorSpaceExtension = EglExtensions.displayP3; + } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) { + hasWideColorSpaceExtension = EglExtensions.scRGB; + } else { + LOG_ALWAYS_FATAL("Unsupported wide color space."); } + mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension && + mEglConfigWideGamut != EGL_NO_CONFIG_KHR; +} + +EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) { + EGLint eglSwapBehavior = + (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + EGLint attribs[] = {EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 0, + EGL_CONFIG_CAVEAT, + EGL_NONE, + EGL_STENCIL_SIZE, + STENCIL_BUFFER_SIZE, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | eglSwapBehavior, + EGL_NONE}; + EGLConfig config = EGL_NO_CONFIG_KHR; + EGLint numConfigs = 1; + if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) || numConfigs != 1) { + return EGL_NO_CONFIG_KHR; + } + return config; +} + +EGLConfig EglManager::loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior) { + EGLint eglSwapBehavior = + (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + // If we reached this point, we have a valid swap behavior + EGLint attribs[] = {EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_COLOR_COMPONENT_TYPE_EXT, + EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT, + EGL_RED_SIZE, + 16, + EGL_GREEN_SIZE, + 16, + EGL_BLUE_SIZE, + 16, + EGL_ALPHA_SIZE, + 16, + EGL_DEPTH_SIZE, + 0, + EGL_STENCIL_SIZE, + STENCIL_BUFFER_SIZE, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | eglSwapBehavior, + EGL_NONE}; + EGLConfig config = EGL_NO_CONFIG_KHR; + EGLint numConfigs = 1; + if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) || numConfigs != 1) { + return EGL_NO_CONFIG_KHR; + } + return config; } void EglManager::initExtensions() { @@ -164,12 +224,10 @@ void EglManager::initExtensions() { EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace"); EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context"); EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float"); -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb_linear"); -#else EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb"); -#endif + EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough"); EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority"); + EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context"); } bool EglManager::hasEglContext() { @@ -177,74 +235,33 @@ bool EglManager::hasEglContext() { } void EglManager::loadConfigs() { - ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); - EGLint swapBehavior = - (mSwapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; - EGLint attribs[] = {EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 8, - EGL_DEPTH_SIZE, - 0, - EGL_CONFIG_CAVEAT, - EGL_NONE, - EGL_STENCIL_SIZE, - Stencil::getStencilSize(), - EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | swapBehavior, - EGL_NONE}; - - EGLint numConfigs = 1; - if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs) || - numConfigs != 1) { + // 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. + mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior); + if (mEglConfig == EGL_NO_CONFIG_KHR) { if (mSwapBehavior == SwapBehavior::Preserved) { // Try again without dirty regions enabled ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); mSwapBehavior = SwapBehavior::Discard; - loadConfigs(); - return; // the call to loadConfigs() we just made picks the wide gamut config + mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior); } else { // Failed to get a valid config LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString()); } } + SkColorType wideColorType = DeviceInfo::get()->getWideColorType(); - if (EglExtensions.pixelFormatFloat) { - // If we reached this point, we have a valid swap behavior - EGLint attribs16F[] = {EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_COLOR_COMPONENT_TYPE_EXT, - EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT, - EGL_RED_SIZE, - 16, - EGL_GREEN_SIZE, - 16, - EGL_BLUE_SIZE, - 16, - EGL_ALPHA_SIZE, - 16, - EGL_DEPTH_SIZE, - 0, - EGL_STENCIL_SIZE, - Stencil::getStencilSize(), - EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | swapBehavior, - EGL_NONE}; - - numConfigs = 1; - if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs, - &numConfigs) || - numConfigs != 1) { + // When we reach this point, we have a valid swap behavior + if (wideColorType == SkColorType::kRGBA_F16_SkColorType && EglExtensions.pixelFormatFloat) { + mEglConfigWideGamut = loadFP16Config(mEglDisplay, mSwapBehavior); + if (mEglConfigWideGamut == EGL_NO_CONFIG_KHR) { ALOGE("Device claims wide gamut support, cannot find matching config, error = %s", - eglErrorString()); + eglErrorString()); EglExtensions.pixelFormatFloat = false; } + } else if (wideColorType == SkColorType::kN32_SkColorType) { + mEglConfigWideGamut = load8BitsConfig(mEglDisplay, mSwapBehavior); } } @@ -269,17 +286,19 @@ 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(); +Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, + ColorMode colorMode, + sk_sp<SkColorSpace> colorSpace) { + LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized"); - wideColorGamut = wideColorGamut && EglExtensions.glColorSpace && EglExtensions.scRGB && - EglExtensions.pixelFormatFloat && EglExtensions.noConfigContext; + bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport && + 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,17 +308,17 @@ 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 // surface appropriately for SurfaceFlinger: - // - Gamma blending (default) requires the use of the scRGB-nl color space - // - Linear blending requires the use of the scRGB color space + // - Gamma blending (default) requires the use of the non-linear color space + // - Linear blending requires the use of the linear 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 @@ -307,26 +326,27 @@ EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorG if (EglExtensions.glColorSpace) { attribs[0] = EGL_GL_COLORSPACE_KHR; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING if (wideColorGamut) { - attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT; - } else { - attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR; - } -#else - if (wideColorGamut) { - attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + skcms_Matrix3x3 colorGamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) { + attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + } } else { attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; } -#endif } EGLSurface surface = eglCreateWindowSurface( mEglDisplay, wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs); - LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, - "Failed to create EGLSurface for window %p, eglErr = %s", (void*)window, - eglErrorString()); + if (surface == EGL_NO_SURFACE) { + return Error<EGLint>{eglGetError()}; + } if (mSwapBehavior != SwapBehavior::Preserved) { LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, @@ -350,10 +370,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 +384,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 +505,104 @@ 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..27d41d26a73a 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -17,10 +17,15 @@ #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" +#include "utils/Result.h" namespace android { namespace uirenderer { @@ -33,20 +38,25 @@ 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); + Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode, + sk_sp<SkColorSpace> colorSpace); 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 +70,25 @@ 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(); + enum class SwapBehavior { + Discard, + Preserved, + BufferAge, + }; + + static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior); + static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior); void initExtensions(); void createPBufferSurface(); @@ -72,21 +96,13 @@ private: void createContext(); EGLint queryBufferAge(EGLSurface surface); - RenderThread& mRenderThread; - EGLDisplay mEglDisplay; EGLConfig mEglConfig; EGLConfig mEglConfigWideGamut; EGLContext mEglContext; EGLSurface mPBufferSurface; - EGLSurface mCurrentSurface; - - enum class SwapBehavior { - Discard, - Preserved, - BufferAge, - }; + bool mHasWideColorGamutSupport; SwapBehavior mSwapBehavior = SwapBehavior::Discard; }; diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index b1de49733c09..3b81014c05e2 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,29 +16,38 @@ #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> class GrContext; -namespace android { +struct ANativeWindow; -class Surface; +namespace android { 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,33 @@ 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, - 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) = 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 bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode, + uint32_t extraBuffers) = 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 TaskManager* getTaskManager() = 0; + virtual void renderLayers(const LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const LightInfo& lightInfo) = 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 GrSurfaceOrigin getSurfaceOrigin() = 0; + virtual void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) = 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/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp new file mode 100644 index 000000000000..ad1fc4921781 --- /dev/null +++ b/libs/hwui/renderthread/ReliableSurface.cpp @@ -0,0 +1,310 @@ +/* + * 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 "ReliableSurface.h" + +#include <private/android/AHardwareBufferHelpers.h> + +namespace android::uirenderer::renderthread { + +// TODO: Re-enable after addressing more of the TODO's +// With this disabled we won't have a good up-front signal that the surface is no longer valid, +// however we can at least handle that reactively post-draw. There's just not a good mechanism +// to propagate this error back to the caller +constexpr bool DISABLE_BUFFER_PREFETCH = true; + +// TODO: Make surface less protected +// This exists because perform is a varargs, and ANativeWindow has no va_list perform. +// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do +// that instead +struct SurfaceExposer : Surface { + // Make warnings happy + SurfaceExposer() = delete; + + using Surface::cancelBuffer; + using Surface::dequeueBuffer; + using Surface::lockBuffer_DEPRECATED; + using Surface::perform; + using Surface::queueBuffer; + using Surface::setBufferCount; + using Surface::setSwapInterval; +}; + +#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) + +ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { + LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); + + ANativeWindow::setSwapInterval = hook_setSwapInterval; + ANativeWindow::dequeueBuffer = hook_dequeueBuffer; + ANativeWindow::cancelBuffer = hook_cancelBuffer; + ANativeWindow::queueBuffer = hook_queueBuffer; + ANativeWindow::query = hook_query; + ANativeWindow::perform = hook_perform; + + ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; + ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; + ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; + ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; +} + +ReliableSurface::~ReliableSurface() { + clearReservedBuffer(); +} + +void ReliableSurface::perform(int operation, va_list args) { + std::lock_guard _lock{mMutex}; + + switch (operation) { + case NATIVE_WINDOW_SET_USAGE: + mUsage = va_arg(args, uint32_t); + break; + case NATIVE_WINDOW_SET_USAGE64: + mUsage = va_arg(args, uint64_t); + break; + case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: + /* width */ va_arg(args, uint32_t); + /* height */ va_arg(args, uint32_t); + mFormat = va_arg(args, PixelFormat); + break; + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + mFormat = va_arg(args, PixelFormat); + break; + } +} + +int ReliableSurface::reserveNext() { + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + ALOGW("reserveNext called but there was already a buffer reserved?"); + return OK; + } + if (mInErrorState) { + return UNKNOWN_ERROR; + } + if (mHasDequeuedBuffer) { + return OK; + } + if constexpr (DISABLE_BUFFER_PREFETCH) { + return OK; + } + } + + // TODO: Update this to better handle when requested dimensions have changed + // Currently the driver does this via query + perform but that's after we've already + // reserved a buffer. Should we do that logic instead? Or should we drop + // the backing Surface to the ground and go full manual on the IGraphicBufferProducer instead? + + int fenceFd = -1; + ANativeWindowBuffer* buffer = nullptr; + int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd); + + { + std::lock_guard _lock{mMutex}; + LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext"); + mReservedBuffer = buffer; + mReservedFenceFd.reset(fenceFd); + } + + return result; +} + +void ReliableSurface::clearReservedBuffer() { + ANativeWindowBuffer* buffer = nullptr; + int releaseFd = -1; + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + ALOGW("Reserved buffer %p was never used", mReservedBuffer); + buffer = mReservedBuffer; + releaseFd = mReservedFenceFd.release(); + } + mReservedBuffer = nullptr; + mReservedFenceFd.reset(); + mHasDequeuedBuffer = false; + } + if (buffer) { + callProtected(mSurface, cancelBuffer, buffer, releaseFd); + } +} + +int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + clearReservedBuffer(); + if (isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } + int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); + return result; +} + +int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + *buffer = mReservedBuffer; + *fenceFd = mReservedFenceFd.release(); + mReservedBuffer = nullptr; + return OK; + } + } + + int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); + if (result != OK) { + ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); + *buffer = acquireFallbackBuffer(); + *fenceFd = -1; + return *buffer ? OK : INVALID_OPERATION; + } else { + std::lock_guard _lock{mMutex}; + mHasDequeuedBuffer = true; + } + return OK; +} + +int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + clearReservedBuffer(); + + if (isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } + + int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); + return result; +} + +bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { + if (!mScratchBuffer || !windowBuffer) { + return false; + } + ANativeWindowBuffer* scratchBuffer = + AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); + return windowBuffer == scratchBuffer; +} + +ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer() { + std::lock_guard _lock{mMutex}; + mInErrorState = true; + + if (mScratchBuffer) { + return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); + } + + AHardwareBuffer_Desc desc; + desc.usage = mUsage; + desc.format = mFormat; + desc.width = 1; + desc.height = 1; + desc.layers = 1; + desc.rfu0 = 0; + desc.rfu1 = 0; + AHardwareBuffer* newBuffer = nullptr; + int err = AHardwareBuffer_allocate(&desc, &newBuffer); + if (err) { + // Allocate failed, that sucks + ALOGW("Failed to allocate scratch buffer, error=%d", err); + return nullptr; + } + mScratchBuffer.reset(newBuffer); + return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); +} + +Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { + return getSelf(window)->mSurface.get(); +} + +int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { + return callProtected(getWrapped(window), setSwapInterval, interval); +} + +int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, + int* fenceFd) { + return getSelf(window)->dequeueBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, + int fenceFd) { + return getSelf(window)->cancelBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, + int fenceFd) { + return getSelf(window)->queueBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer** buffer) { + ANativeWindowBuffer* buf; + int fenceFd = -1; + int result = window->dequeueBuffer(window, &buf, &fenceFd); + if (result != OK) { + return result; + } + sp<Fence> fence(new Fence(fenceFd)); + int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); + if (waitResult != OK) { + ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); + window->cancelBuffer(window, buf, -1); + return waitResult; + } + *buffer = buf; + return result; +} + +int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + return window->cancelBuffer(window, buffer, -1); +} + +int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + // This method is a no-op in Surface as well + return OK; +} + +int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + return window->queueBuffer(window, buffer, -1); +} + +int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { + return getWrapped(window)->query(what, value); +} + +int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { + // Drop the reserved buffer if there is one since this (probably) mutated buffer dimensions + // TODO: Filter to things that only affect the reserved buffer + // TODO: Can we mutate the reserved buffer in some cases? + getSelf(window)->clearReservedBuffer(); + va_list args; + va_start(args, operation); + int result = callProtected(getWrapped(window), perform, operation, args); + va_end(args); + + va_start(args, operation); + getSelf(window)->perform(operation, args); + va_end(args); + + return result; +} + +}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h new file mode 100644 index 000000000000..0bfc72ef61cb --- /dev/null +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -0,0 +1,87 @@ +/* + * 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/Surface.h> +#include <utils/Macros.h> +#include <utils/StrongPointer.h> + +#include <memory> + +namespace android::uirenderer::renderthread { + +class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> { + PREVENT_COPY_AND_ASSIGN(ReliableSurface); + +public: + ReliableSurface(sp<Surface>&& surface); + ~ReliableSurface(); + + void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); } + + int reserveNext(); + + void allocateBuffers() { mSurface->allocateBuffers(); } + + int query(int what, int* value) const { return mSurface->query(what, value); } + + nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); } + + uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); } + +private: + const sp<Surface> mSurface; + + mutable std::mutex mMutex; + + uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; + PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888; + std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{ + nullptr, AHardwareBuffer_release}; + ANativeWindowBuffer* mReservedBuffer = nullptr; + base::unique_fd mReservedFenceFd; + bool mHasDequeuedBuffer = false; + bool mInErrorState = false; + + bool isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const; + ANativeWindowBuffer* acquireFallbackBuffer(); + void clearReservedBuffer(); + + void perform(int operation, va_list args); + int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); + int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); + int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd); + + static Surface* getWrapped(const ANativeWindow*); + + // ANativeWindow hooks + static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); + static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, + int* fenceFd); + static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); + + static int hook_perform(ANativeWindow* window, int operation, ...); + static int hook_query(const ANativeWindow* window, int what, int* value); + static int hook_setSwapInterval(ANativeWindow* window, int interval); + + static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer); + static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); +}; + +}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 020761110ef0..1a1b9dac37f6 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -21,6 +21,8 @@ #include "Properties.h" #include "Readback.h" #include "Rect.h" +#include "WebViewFunctorManager.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/VectorDrawableAtlas.h" #include "renderstate/RenderState.h" #include "renderthread/CanvasContext.h" @@ -29,6 +31,7 @@ #include "renderthread/RenderThread.h" #include "utils/Macros.h" #include "utils/TimeUtils.h" +#include "utils/TraceUtils.h" #include <ui/GraphicBuffer.h> @@ -65,10 +68,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 +82,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)); }); + [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 +99,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) { @@ -151,6 +145,12 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) { } } +void RenderProxy::destroyFunctor(int functor) { + ATRACE_CALL(); + RenderThread& thread = RenderThread::getInstance(); + thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); }); +} + DeferredLayerUpdater* RenderProxy::createTextureLayer() { return mRenderThread.queue().runSync([this]() -> auto { return mContext->createTextureLayer(); @@ -162,8 +162,11 @@ void RenderProxy::buildLayer(RenderNode* node) { } bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) { - return mRenderThread.queue().runSync( - [&]() -> bool { return mContext->copyLayerInto(layer, &bitmap); }); + ATRACE_NAME("TextureView#getBitmap"); + auto& thread = RenderThread::getInstance(); + return thread.queue().runSync([&]() -> bool { + return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success; + }); } void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -200,8 +203,10 @@ 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() { @@ -238,13 +243,15 @@ uint32_t RenderProxy::frameTimePercentile(int percentile) { } void RenderProxy::dumpGraphicsMemory(int fd) { - auto& thread = RenderThread::getInstance(); - thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); }); + if (RenderThread::hasInstance()) { + auto& thread = RenderThread::getInstance(); + thread.queue().runSync([&]() { thread.dumpGraphicsMemory(fd); }); + } } void RenderProxy::setProcessStatsBuffer(int fd) { auto& rt = RenderThread::getInstance(); - rt.queue().post([&rt, fd = dup(fd) ]() { + rt.queue().post([&rt, fd = dup(fd)]() { rt.globalProfileData().switchStorageToAshmem(fd); close(fd); }); @@ -275,6 +282,12 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } +void RenderProxy::setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback) { + mRenderThread.queue().post( + [this, cb = callback]() { mContext->setPictureCapturedCallback(cb); }); +} + void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) { mDrawFrameTask.setFrameCallback(std::move(callback)); } @@ -283,22 +296,27 @@ 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} ]() { + mRenderThread.queue().post([this, observer = sp{observerPtr}]() { mContext->addFrameMetricsObserver(observer.get()); }); } void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) { - mRenderThread.queue().post([ this, observer = sp{observerPtr} ]() { + mRenderThread.queue().post([this, observer = sp{observerPtr}]() { mContext->removeFrameMetricsObserver(observer.get()); }); } +void RenderProxy::setForceDark(bool enable) { + mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); }); +} + +void RenderProxy::setRenderAheadDepth(int renderAhead) { + mRenderThread.queue().post( + [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); }); +} + int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); @@ -334,30 +352,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) { + ATRACE_NAME("HardwareBitmap readback"); 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 thread.queue().runSync( + [&]() -> int { 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; } @@ -383,6 +389,12 @@ void RenderProxy::releaseVDAtlasEntries() { }); } +void RenderProxy::preload() { + // Create RenderThread object and start the thread. Then preload Vulkan/EGL driver. + auto& thread = RenderThread::getInstance(); + thread.queue().post([&thread]() { thread.preload(); }); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 0d29b4bcc317..a0f08cbd26f9 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -49,7 +49,7 @@ enum { Reset = 1 << 1, JankStats = 1 << 2, }; -}; +} /* * RenderProxy is strictly single threaded. All methods must be invoked on the owning @@ -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(); @@ -83,6 +82,7 @@ public: ANDROID_API void destroy(); ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion); + static void destroyFunctor(int functor); ANDROID_API DeferredLayerUpdater* createTextureLayer(); ANDROID_API void buildLayer(RenderNode* node); @@ -96,7 +96,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,31 +110,46 @@ 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); ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom); + ANDROID_API void setPictureCapturedCallback( + const std::function<void(sk_sp<SkPicture>&&)>& callback); ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback); ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback); ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer); ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer); - ANDROID_API long getDroppedFrameReportCount(); + ANDROID_API void setForceDark(bool enable); + + /** + * Sets a render-ahead depth on the backing renderer. This will increase latency by + * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>. + * In return the renderer will be less susceptible to jitter, resulting in a smoother animation. + * + * Not recommended to use in response to anything touch driven, but for canned animations + * where latency is not a concern careful use may be beneficial. + * + * Note that when increasing this there will be a frame gap of N frames where N is + * renderAhead - <current renderAhead>. When decreasing this if there are any pending + * frames they will retain their prior renderAhead value, so it will take a few frames + * for the decrease to flush through. + * + * @param renderAhead How far to render ahead, must be in the range [0..2] + */ + ANDROID_API void setRenderAheadDepth(int renderAhead); 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(); + ANDROID_API static void preload(); + static void repackVectorDrawableAtlas(); static void releaseVDAtlasEntries(); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6a2a025da121..41cb8fdc66bd 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -16,27 +16,34 @@ #include "RenderThread.h" +#include "../HardwareBitmapUploader.h" #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" +#include "utils/TraceUtils.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> #include <utils/Log.h> #include <utils/Mutex.h> +#include <thread> namespace android { namespace uirenderer { @@ -47,17 +54,16 @@ namespace renderthread { // using just a few large reads. static const size_t EVENT_BUFFER_SIZE = 100; -// Slight delay to give the UI time to push us a new frame before we replay -static const nsecs_t DISPATCH_FRAME_CALLBACKS_DELAY = milliseconds_to_nanoseconds(4); - static bool gHasRenderThreadInstance = false; -static void (*gOnStartHook)() = nullptr; +static JVMAttachHook gOnStartHook = nullptr; class DisplayEventReceiverWrapper : public VsyncSource { public: - DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver) - : mDisplayEventReceiver(std::move(receiver)) {} + DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver, + const std::function<void()>& onDisplayConfigChanged) + : mDisplayEventReceiver(std::move(receiver)) + , mOnDisplayConfigChanged(onDisplayConfigChanged) {} virtual void requestNextVsync() override { status_t status = mDisplayEventReceiver->requestNextVsync(); @@ -75,6 +81,9 @@ public: case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: latest = ev.header.timestamp; break; + case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED: + mOnDisplayConfigChanged(); + break; } } } @@ -86,6 +95,7 @@ public: private: std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver; + std::function<void()> mOnDisplayConfigChanged; }; class DummyVsyncSource : public VsyncSource { @@ -93,14 +103,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; @@ -110,11 +117,15 @@ bool RenderThread::hasInstance() { return gHasRenderThreadInstance; } -void RenderThread::setOnStartHook(void (*onStartHook)()) { +void RenderThread::setOnStartHook(JVMAttachHook onStartHook) { LOG_ALWAYS_FATAL_IF(hasInstance(), "can't set an onStartHook after we've started..."); gOnStartHook = onStartHook; } +JVMAttachHook RenderThread::getOnStartHook() { + return gOnStartHook; +} + RenderThread& RenderThread::getInstance() { // This is a pointer because otherwise __cxa_finalize // will try to delete it like a Good Citizen but that causes us to crash @@ -131,6 +142,7 @@ RenderThread::RenderThread() , mFrameCallbackTaskPending(false) , mRenderState(nullptr) , mEglManager(nullptr) + , mFunctorManager(WebViewFunctorManager::instance()) , mVkManager(nullptr) { Properties::load(); start("RenderThread"); @@ -147,28 +159,94 @@ 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); - mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver)); + RenderThread::displayEventReceiverCallback, this); + mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver), [this] { + DeviceInfo::get()->onDisplayConfigChanged(); + setupFrameInterval(); + }); } else { mVsyncSource = new DummyVsyncSource(this); } } void RenderThread::initThreadLocals() { - mDisplayInfo = DeviceInfo::queryDisplayInfo(); - nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps); - mTimeLord.setFrameInterval(frameIntervalNanos); + setupFrameInterval(); initializeDisplayEventReceiver(); - mEglManager = new EglManager(*this); + mEglManager = new EglManager(); mRenderState = new RenderState(*this); - mVkManager = new VulkanManager(*this); - mCacheManager = new CacheManager(mDisplayInfo); + mVkManager = new VulkanManager(); + mCacheManager = new CacheManager(DeviceInfo::get()->displayInfo()); +} + +void RenderThread::setupFrameInterval() { + auto& displayInfo = DeviceInfo::get()->displayInfo(); + nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / displayInfo.fps); + mTimeLord.setFrameInterval(frameIntervalNanos); + mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f); +} + +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; + initGrContextOptions(options); + 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::requireVkContext() { + if (mVkManager->hasVkContext()) { + return; + } + mVkManager->initialize(); + GrContextOptions options; + initGrContextOptions(options); + auto vkDriverVersion = mVkManager->getDriverVersion(); + cacheManager().configureContext(&options, &vkDriverVersion, sizeof(vkDriverVersion)); + sk_sp<GrContext> grContext = mVkManager->createContext(options); + LOG_ALWAYS_FATAL_IF(!grContext.get()); + setGrContext(grContext); +} + +void RenderThread::initGrContextOptions(GrContextOptions& options) { + options.fPreferExternalImagesOverES3 = true; + options.fDisableDistanceFieldPaths = true; +} + +void RenderThread::destroyRenderingContext() { + mFunctorManager.onContextDestroyed(); + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + if (mEglManager->hasEglContext()) { + setGrContext(nullptr); + mEglManager->destroy(); + } + } else { + if (vulkanManager().hasVkContext()) { + setGrContext(nullptr); + vulkanManager().destroy(); + } + } } void RenderThread::dumpGraphicsMemory(int fd) { @@ -178,16 +256,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 +277,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 +286,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) { @@ -265,7 +324,7 @@ void RenderThread::drainDisplayEventQueue() { if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) { ATRACE_NAME("queue mFrameCallbackTask"); mFrameCallbackTaskPending = true; - nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY); + nsecs_t runAt = (vsyncEvent + mDispatchFrameDelay); queue().postAt(runAt, [this]() { dispatchFrameCallbacks(); }); } } @@ -298,8 +357,9 @@ void RenderThread::requestVsync() { bool RenderThread::threadLoop() { setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY); + Looper::setForThread(mLooper); if (gOnStartHook) { - gOnStartHook(); + gOnStartHook("RenderThread"); } initThreadLocals(); @@ -347,10 +407,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: @@ -364,6 +420,17 @@ bool RenderThread::isCurrent() { return gettid() == getInstance().getTid(); } +void RenderThread::preload() { + // EGL driver is always preloaded only if HWUI renders with GL. + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); }); + eglInitThread.detach(); + } else { + requireVkContext(); + } + HardwareBitmapUploader::initialize(); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index e9c264917905..c96e284df6b4 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -22,7 +22,9 @@ #include "../JankTracker.h" #include "CacheManager.h" #include "TimeLord.h" +#include "WebViewFunctorManager.h" #include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" #include <GrContext.h> #include <SkBitmap.h> @@ -39,6 +41,7 @@ namespace android { class Bitmap; +class AutoBackendTextureRelease; namespace uirenderer { @@ -46,6 +49,10 @@ class Readback; class RenderState; class TestUtils; +namespace skiapipeline { +class VkFunctorDrawHandler; +} + namespace renderthread { class CanvasContext; @@ -70,12 +77,15 @@ struct VsyncSource { class DummyVsyncSource; +typedef void (*JVMAttachHook)(const char* name); + class RenderThread : private ThreadBase { PREVENT_COPY_AND_ASSIGN(RenderThread); public: - // Sets a callback that fires before any RenderThread setup has occured. - ANDROID_API static void setOnStartHook(void (*onStartHook)()); + // Sets a callback that fires before any RenderThread setup has occurred. + ANDROID_API static void setOnStartHook(JVMAttachHook onStartHook); + static JVMAttachHook getOnStartHook(); WorkQueue& queue() { return ThreadBase::queue(); } @@ -92,8 +102,6 @@ public: ProfileDataContainer& globalProfileData() { return mGlobalProfileData; } Readback& readback(); - const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; } - GrContext* getGrContext() const { return mGrContext.get(); } void setGrContext(sk_sp<GrContext> cxt); @@ -103,6 +111,12 @@ public: sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap); void dumpGraphicsMemory(int fd); + void requireGlContext(); + void requireVkContext(); + void destroyRenderingContext(); + + void preload(); + /** * isCurrent provides a way to query, if the caller is running on * the render thread. @@ -111,6 +125,8 @@ public: */ static bool isCurrent(); + static void initGrContextOptions(GrContextOptions& options); + protected: virtual bool threadLoop() override; @@ -118,7 +134,10 @@ private: friend class DispatchFrameCallbacks; friend class RenderProxy; friend class DummyVsyncSource; + friend class android::AutoBackendTextureRelease; friend class android::uirenderer::TestUtils; + friend class android::uirenderer::WebViewFunctor; + friend class android::uirenderer::skiapipeline::VkFunctorDrawHandler; RenderThread(); virtual ~RenderThread(); @@ -128,13 +147,12 @@ private: void initThreadLocals(); void initializeDisplayEventReceiver(); + void setupFrameInterval(); static int displayEventReceiverCallback(int fd, int events, void* data); void drainDisplayEventQueue(); void dispatchFrameCallbacks(); void requestVsync(); - DisplayInfo mDisplayInfo; - VsyncSource* mVsyncSource; bool mVsyncRequested; std::set<IFrameCallback*> mFrameCallbacks; @@ -146,8 +164,10 @@ private: bool mFrameCallbackTaskPending; TimeLord mTimeLord; + nsecs_t mDispatchFrameDelay = 4_ms; RenderState* mRenderState; EglManager* mEglManager; + WebViewFunctorManager& mFunctorManager; ProfileDataContainer mGlobalProfileData; Readback* mReadback = nullptr; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 21c91a26745b..5173f638068d 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -16,64 +16,310 @@ #include "VulkanManager.h" -#include "DeviceInfo.h" +#include <android/sync.h> +#include <gui/Surface.h> + #include "Properties.h" #include "RenderThread.h" #include "renderstate/RenderState.h" #include "utils/FatVector.h" +#include "utils/TraceUtils.h" +#include <GrBackendSemaphore.h> #include <GrBackendSurface.h> #include <GrContext.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) - -VulkanManager::VulkanManager(RenderThread& thread) : mRenderThread(thread) {} +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 VulkanManager::destroy() { - if (!hasVkContext()) return; + void* pNext = features.pNext; + while (pNext) { + void* current = pNext; + pNext = static_cast<CommonVulkanHeader*>(current)->pNext; + free(current); + } +} - mRenderThread.renderState().onVkContextDestroyed(); - mRenderThread.setGrContext(nullptr); +#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) +void VulkanManager::destroy() { 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; + mInstanceExtensionsOwner.clear(); + mInstanceExtensions.clear(); + mDeviceExtensionsOwner.clear(); + mDeviceExtensions.clear(); + free_features_extensions_structs(mPhysicalDeviceFeatures2); + mPhysicalDeviceFeatures2 = {}; } -void VulkanManager::initialize() { - if (hasVkContext()) { - return; +void 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 + mAPIVersion, // apiVersion + }; + + { + GET_PROC(EnumerateInstanceExtensionProperties); + + uint32_t extensionCount = 0; + err = mEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); + mInstanceExtensionsOwner.resize(extensionCount); + err = mEnumerateInstanceExtensionProperties(nullptr, &extensionCount, + mInstanceExtensionsOwner.data()); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); + bool hasKHRSurfaceExtension = false; + bool hasKHRAndroidSurfaceExtension = false; + for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) { + mInstanceExtensions.push_back(extension.extensionName); + if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { + hasKHRSurfaceExtension = true; + } + if (!strcmp(extension.extensionName, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME)) { + hasKHRAndroidSurfaceExtension = true; + } + } + LOG_ALWAYS_FATAL_IF(!hasKHRSurfaceExtension || !hasKHRAndroidSurfaceExtension); + } + + const VkInstanceCreateInfo instance_create = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + &app_info, // pApplicationInfo + 0, // enabledLayerNameCount + nullptr, // ppEnabledLayerNames + (uint32_t)mInstanceExtensions.size(), // enabledExtensionNameCount + mInstanceExtensions.data(), // ppEnabledExtensionNames + }; + + GET_PROC(CreateInstance); + err = mCreateInstance(&instance_create, nullptr, &mInstance); + LOG_ALWAYS_FATAL_IF(err < 0); + + GET_INST_PROC(DestroyInstance); + GET_INST_PROC(EnumeratePhysicalDevices); + GET_INST_PROC(GetPhysicalDeviceProperties); + GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties); + GET_INST_PROC(GetPhysicalDeviceFeatures2); + GET_INST_PROC(GetPhysicalDeviceImageFormatProperties2); + 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; + LOG_ALWAYS_FATAL_IF(mEnumeratePhysicalDevices(mInstance, &gpuCount, nullptr)); + LOG_ALWAYS_FATAL_IF(!gpuCount); + // 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. + LOG_ALWAYS_FATAL_IF(err && VK_INCOMPLETE != err); + + VkPhysicalDeviceProperties physDeviceProperties; + mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties); + LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0)); + mDriverVersion = physDeviceProperties.driverVersion; + + // query to get the initial queue props size + uint32_t queueCount; + mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr); + LOG_ALWAYS_FATAL_IF(!queueCount); + + // 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; + } + } + LOG_ALWAYS_FATAL_IF(mGraphicsQueueIndex == queueCount); + + // 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; + + { + uint32_t extensionCount = 0; + err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount, + nullptr); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); + mDeviceExtensionsOwner.resize(extensionCount); + err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount, + mDeviceExtensionsOwner.data()); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); + bool hasKHRSwapchainExtension = false; + for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) { + mDeviceExtensions.push_back(extension.extensionName); + if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { + hasKHRSwapchainExtension = true; + } + } + LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension); } - auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; }; - - mBackendContext.reset(GrVkBackendContext::Create(vkGetInstanceProcAddr, vkGetDeviceProcAddr, - &mPresentQueueIndex, canPresent)); - LOG_ALWAYS_FATAL_IF(!mBackendContext.get()); - - // 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); - GET_DEV_PROC(CreateSwapchainKHR); - GET_DEV_PROC(DestroySwapchainKHR); - GET_DEV_PROC(GetSwapchainImagesKHR); - GET_DEV_PROC(AcquireNextImageKHR); - GET_DEV_PROC(QueuePresentKHR); + 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, mInstanceExtensions.size(), + mInstanceExtensions.data(), mDeviceExtensions.size(), + mDeviceExtensions.data()); + + LOG_ALWAYS_FATAL_IF(!grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)); + + 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; + } + + VkPhysicalDeviceSamplerYcbcrConversionFeatures* ycbcrFeature; + ycbcrFeature = (VkPhysicalDeviceSamplerYcbcrConversionFeatures*)malloc( + sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures)); + LOG_ALWAYS_FATAL_IF(!ycbcrFeature); + ycbcrFeature->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + ycbcrFeature->pNext = nullptr; + *tailPNext = ycbcrFeature; + tailPNext = &ycbcrFeature->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}; + + void* queueNextPtr = nullptr; + + VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo; + + if (Properties::contextPriority != 0 && + grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) { + memset(&queuePriorityCreateInfo, 0, sizeof(VkDeviceQueueGlobalPriorityCreateInfoEXT)); + queuePriorityCreateInfo.sType = + VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT; + queuePriorityCreateInfo.pNext = nullptr; + switch (Properties::contextPriority) { + case EGL_CONTEXT_PRIORITY_LOW_IMG: + queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT; + break; + case EGL_CONTEXT_PRIORITY_MEDIUM_IMG: + queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT; + break; + case EGL_CONTEXT_PRIORITY_HIGH_IMG: + queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT; + break; + default: + LOG_ALWAYS_FATAL("Unsupported context priority"); + } + queueNextPtr = &queuePriorityCreateInfo; + } + + const VkDeviceQueueCreateInfo queueInfo[2] = { + { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + queueNextPtr, // pNext + 0, // VkDeviceQueueCreateFlags + mGraphicsQueueIndex, // queueFamilyIndex + 1, // queueCount + queuePriorities, // pQueuePriorities + }, + { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + queueNextPtr, // 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)mDeviceExtensions.size(), // extensionCount + mDeviceExtensions.data(), // ppEnabledExtensionNames + nullptr, // ppEnabledFeatures + }; + + LOG_ALWAYS_FATAL_IF(mCreateDevice(mPhysicalDevice, &deviceInfo, nullptr, &mDevice)); + + GET_DEV_PROC(GetDeviceQueue); + GET_DEV_PROC(DeviceWaitIdle); + GET_DEV_PROC(DestroyDevice); GET_DEV_PROC(CreateCommandPool); GET_DEV_PROC(DestroyCommandPool); GET_DEV_PROC(AllocateCommandBuffers); @@ -88,10 +334,27 @@ 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); +} + +void VulkanManager::initialize() { + if (mDevice != VK_NULL_HANDLE) { + return; + } + + GET_PROC(EnumerateInstanceVersion); + uint32_t instanceVersion; + LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion)); + LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0)); + + this->setupDevice(mExtensions, mPhysicalDeviceFeatures2); + + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); // create the command pool for the command buffers if (VK_NULL_HANDLE == mCommandPool) { @@ -99,602 +362,312 @@ void VulkanManager::initialize() { 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); - mGetDeviceQueue(mBackendContext->fDevice, mPresentQueueIndex, 0, &mPresentQueue); - - GrContextOptions options; - options.fDisableDistanceFieldPaths = true; - mRenderThread.cacheManager().configureContext(&options); - sk_sp<GrContext> grContext(GrContext::MakeVulkan(mBackendContext, options)); - LOG_ALWAYS_FATAL_IF(!grContext.get()); - mRenderThread.setGrContext(grContext); - DeviceInfo::initialize(mRenderThread.getGrContext()->caps()->maxRenderTargetSize()); + mGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue); 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 -// previous uses have finished before returning. -VulkanSurface::BackbufferInfo* VulkanManager::getAvailableBackbuffer(VulkanSurface* surface) { - SkASSERT(surface->mBackbuffers); - - ++surface->mCurrentBackbufferIndex; - if (surface->mCurrentBackbufferIndex > surface->mImageCount) { - surface->mCurrentBackbufferIndex = 0; - } - - VulkanSurface::BackbufferInfo* backbuffer = - surface->mBackbuffers + surface->mCurrentBackbufferIndex; - - // 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); - if (res != VK_SUCCESS) { - return nullptr; - } +sk_sp<GrContext> VulkanManager::createContext(const GrContextOptions& options) { + 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); + }; - return backbuffer; + GrVkBackendContext backendContext; + backendContext.fInstance = mInstance; + backendContext.fPhysicalDevice = mPhysicalDevice; + backendContext.fDevice = mDevice; + backendContext.fQueue = mGraphicsQueue; + backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; + backendContext.fMaxAPIVersion = mAPIVersion; + backendContext.fVkExtensions = &mExtensions; + backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2; + backendContext.fGetProc = std::move(getProc); + + return GrContext::MakeVulkan(backendContext, options); } -SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { - VulkanSurface::BackbufferInfo* backbuffer = getAvailableBackbuffer(surface); - SkASSERT(backbuffer); - - VkResult res; - - res = mResetFences(mBackendContext->fDevice, 2, backbuffer->mUsageFences); - SkASSERT(VK_SUCCESS == res); +VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { + return VkFunctorInitParams{ + .instance = mInstance, + .physical_device = mPhysicalDevice, + .device = mDevice, + .queue = mGraphicsQueue, + .graphics_queue_index = mGraphicsQueueIndex, + .api_version = mAPIVersion, + .enabled_instance_extension_names = mInstanceExtensions.data(), + .enabled_instance_extension_names_length = + static_cast<uint32_t>(mInstanceExtensions.size()), + .enabled_device_extension_names = mDeviceExtensions.data(), + .enabled_device_extension_names_length = + static_cast<uint32_t>(mDeviceExtensions.size()), + .device_features_2 = &mPhysicalDeviceFeatures2, + }; +} - // 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, - backbuffer->mAcquireSemaphore, VK_NULL_HANDLE, - &backbuffer->mImageIndex); +Frame VulkanManager::dequeueNextBuffer(VulkanSurface* surface) { + VulkanSurface::NativeBufferInfo* bufferInfo = surface->dequeueNativeBuffer(); - if (VK_ERROR_SURFACE_LOST_KHR == res) { - // need to figure out how to create a new vkSurface without the platformData* - // maybe use attach somehow? but need a Window - return nullptr; + if (bufferInfo == nullptr) { + ALOGE("VulkanSurface::dequeueNativeBuffer called with an invalid surface!"); + return Frame(-1, -1, 0); } - if (VK_ERROR_OUT_OF_DATE_KHR == res) { - // tear swapchain down and try again - if (!createSwapchain(surface)) { - return nullptr; - } - backbuffer = getAvailableBackbuffer(surface); - res = mResetFences(mBackendContext->fDevice, 2, backbuffer->mUsageFences); - SkASSERT(VK_SUCCESS == res); - // acquire the image - res = mAcquireNextImageKHR(mBackendContext->fDevice, surface->mSwapchain, UINT64_MAX, - backbuffer->mAcquireSemaphore, VK_NULL_HANDLE, - &backbuffer->mImageIndex); + LOG_ALWAYS_FATAL_IF(!bufferInfo->dequeued); - if (VK_SUCCESS != res) { - return nullptr; + if (bufferInfo->dequeue_fence != -1) { + struct sync_file_info* finfo = sync_file_info(bufferInfo->dequeue_fence); + bool isSignalPending = false; + if (finfo != NULL) { + isSignalPending = finfo->status != 1; + sync_file_info_free(finfo); } - } - - // 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 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; - - VkImageMemoryBarrier imageMemoryBarrier = { - VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType - NULL, // pNext - srcAccessMask, // outputMask - dstAccessMask, // inputMask - layout, // oldLayout - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // newLayout - mPresentQueueIndex, // srcQueueFamilyIndex - mBackendContext->fGraphicsQueueIndex, // dstQueueFamilyIndex - surface->mImages[backbuffer->mImageIndex], // image - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1} // subresourceRange - }; - mResetCommandBuffer(backbuffer->mTransitionCmdBuffers[0], 0); - - VkCommandBufferBeginInfo info; - memset(&info, 0, sizeof(VkCommandBufferBeginInfo)); - info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - info.flags = 0; - mBeginCommandBuffer(backbuffer->mTransitionCmdBuffers[0], &info); - - mCmdPipelineBarrier(backbuffer->mTransitionCmdBuffers[0], srcStageMask, dstStageMask, 0, 0, - nullptr, 0, nullptr, 1, &imageMemoryBarrier); - - mEndCommandBuffer(backbuffer->mTransitionCmdBuffers[0]); - - VkPipelineStageFlags waitDstStageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - // insert the layout transfer into the queue and wait on the acquire - 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 = &backbuffer->mAcquireSemaphore; - submitInfo.pWaitDstStageMask = &waitDstStageFlags; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &backbuffer->mTransitionCmdBuffers[0]; - 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]); - - // 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); - - surface->mBackbuffer = std::move(skSurface); - return surface->mBackbuffer.get(); -} - -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); - 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); + if (isSignalPending) { + int fence_clone = dup(bufferInfo->dequeue_fence); + if (fence_clone == -1) { + ALOGE("dup(fence) failed, stalling until signalled: %s (%d)", strerror(errno), + errno); + sync_wait(bufferInfo->dequeue_fence, -1 /* forever */); + } else { + 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); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err, "Failed to create import semaphore, err: %d", + err); + + 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 = fence_clone; + + err = mImportSemaphoreFdKHR(mDevice, &importInfo); + LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err, "Failed to import semaphore, err: %d", err); + + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(semaphore); + bufferInfo->skSurface->wait(1, &backendSemaphore); + // The following flush blocks the GPU immediately instead of waiting for other + // drawing ops. It seems dequeue_fence is not respected otherwise. + // TODO: remove the flush after finding why backendSemaphore is not working. + bufferInfo->skSurface->flush(); + } } } - delete[] surface->mBackbuffers; - surface->mBackbuffers = nullptr; - delete[] surface->mImageInfos; - surface->mImageInfos = nullptr; - delete[] surface->mImages; - surface->mImages = nullptr; + int bufferAge = (mSwapBehavior == SwapBehavior::Discard) ? 0 : surface->getCurrentBuffersAge(); + return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge); } -void VulkanManager::destroySurface(VulkanSurface* surface) { - // Make sure all submit commands have finished before starting to destroy objects. - if (VK_NULL_HANDLE != mPresentQueue) { - mQueueWaitIdle(mPresentQueue); - } - mDeviceWaitIdle(mBackendContext->fDevice); +struct DestroySemaphoreInfo { + PFN_vkDestroySemaphore mDestroyFunction; + VkDevice mDevice; + VkSemaphore mSemaphore; - destroyBuffers(surface); + DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, + VkSemaphore semaphore) + : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {} +}; - if (VK_NULL_HANDLE != surface->mSwapchain) { - mDestroySwapchainKHR(mBackendContext->fDevice, surface->mSwapchain, nullptr); - surface->mSwapchain = VK_NULL_HANDLE; - } +static void destroy_semaphore(void* context) { + DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context); + info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr); + delete info; +} - if (VK_NULL_HANDLE != surface->mVkSurface) { - mDestroySurfaceKHR(mBackendContext->fInstance, surface->mVkSurface, nullptr); - surface->mVkSurface = VK_NULL_HANDLE; +void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) { + if (CC_UNLIKELY(Properties::waitForGpuCompletion)) { + ATRACE_NAME("Finishing GPU work"); + mDeviceWaitIdle(mDevice); } - delete surface; -} -void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExtent2D extent) { - mGetSwapchainImagesKHR(mBackendContext->fDevice, surface->mSwapchain, &surface->mImageCount, - nullptr); - SkASSERT(surface->mImageCount); - surface->mImages = new VkImage[surface->mImageCount]; - mGetSwapchainImagesKHR(mBackendContext->fDevice, surface->mSwapchain, &surface->mImageCount, - surface->mImages); - - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - - // set up initial image layouts and create surfaces - surface->mImageInfos = new VulkanSurface::ImageInfo[surface->mImageCount]; - for (uint32_t i = 0; i < surface->mImageCount; ++i) { - GrVkImageInfo info; - info.fImage = surface->mImages[i]; - info.fAlloc = GrVkAlloc(); - info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; - info.fImageTiling = VK_IMAGE_TILING_OPTIMAL; - info.fFormat = format; - info.fLevelCount = 1; - - GrBackendRenderTarget backendRT(extent.width, extent.height, 0, 0, info); - - VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i]; - imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, nullptr, &props); + VulkanSurface::NativeBufferInfo* bufferInfo = surface->getCurrentBufferInfo(); + if (!bufferInfo) { + // If VulkanSurface::dequeueNativeBuffer failed earlier, then swapBuffers is a no-op. + return; } - SkASSERT(mCommandPool != VK_NULL_HANDLE); + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - // set up the backbuffers VkSemaphoreCreateInfo semaphoreInfo; - memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo)); semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreInfo.pNext = nullptr; + semaphoreInfo.pNext = &exportInfo; semaphoreInfo.flags = 0; - 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 = 2; - VkFenceCreateInfo fenceInfo; - memset(&fenceInfo, 0, sizeof(VkFenceCreateInfo)); - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.pNext = nullptr; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - // we create one additional backbuffer structure here, because we want to - // give the command buffers they contain a chance to finish before we cycle back - surface->mBackbuffers = new VulkanSurface::BackbufferInfo[surface->mImageCount + 1]; - 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, - &surface->mBackbuffers[i].mAcquireSemaphore); - SkDEBUGCODE(res =) mCreateSemaphore(mBackendContext->fDevice, &semaphoreInfo, nullptr, - &surface->mBackbuffers[i].mRenderSemaphore); - SkDEBUGCODE(res =) mAllocateCommandBuffers(mBackendContext->fDevice, &commandBuffersInfo, - surface->mBackbuffers[i].mTransitionCmdBuffers); - SkDEBUGCODE(res =) mCreateFence(mBackendContext->fDevice, &fenceInfo, nullptr, - &surface->mBackbuffers[i].mUsageFences[0]); - SkDEBUGCODE(res =) mCreateFence(mBackendContext->fDevice, &fenceInfo, nullptr, - &surface->mBackbuffers[i].mUsageFences[1]); - SkASSERT(VK_SUCCESS == res); - } - surface->mCurrentBackbufferIndex = surface->mImageCount; -} - -bool VulkanManager::createSwapchain(VulkanSurface* surface) { - // check for capabilities - VkSurfaceCapabilitiesKHR caps; - VkResult res = mGetPhysicalDeviceSurfaceCapabilitiesKHR(mBackendContext->fPhysicalDevice, - surface->mVkSurface, &caps); - if (VK_SUCCESS != res) { - return false; - } - - uint32_t surfaceFormatCount; - res = mGetPhysicalDeviceSurfaceFormatsKHR(mBackendContext->fPhysicalDevice, surface->mVkSurface, - &surfaceFormatCount, nullptr); - if (VK_SUCCESS != res) { - return false; - } - - FatVector<VkSurfaceFormatKHR, 4> surfaceFormats(surfaceFormatCount); - res = mGetPhysicalDeviceSurfaceFormatsKHR(mBackendContext->fPhysicalDevice, surface->mVkSurface, - &surfaceFormatCount, surfaceFormats.data()); - if (VK_SUCCESS != res) { - return false; - } - - uint32_t presentModeCount; - res = mGetPhysicalDeviceSurfacePresentModesKHR(mBackendContext->fPhysicalDevice, - surface->mVkSurface, &presentModeCount, nullptr); - if (VK_SUCCESS != res) { - return false; - } - - FatVector<VkPresentModeKHR, VK_PRESENT_MODE_RANGE_SIZE_KHR> presentModes(presentModeCount); - res = mGetPhysicalDeviceSurfacePresentModesKHR(mBackendContext->fPhysicalDevice, - surface->mVkSurface, &presentModeCount, - presentModes.data()); - if (VK_SUCCESS != res) { - return false; - } - - VkExtent2D extent = caps.currentExtent; - // clamp width; to handle currentExtent of -1 and protect us from broken hints - if (extent.width < caps.minImageExtent.width) { - extent.width = caps.minImageExtent.width; - } - SkASSERT(extent.width <= caps.maxImageExtent.width); - // clamp height - if (extent.height < caps.minImageExtent.height) { - extent.height = caps.minImageExtent.height; - } - SkASSERT(extent.height <= caps.maxImageExtent.height); - - uint32_t imageCount = caps.minImageCount + 2; - if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) { - // Application must settle for fewer images than desired: - imageCount = caps.maxImageCount; - } - - // Currently Skia requires the images to be color attchments and support all transfer - // operations. - VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT; - SkASSERT((caps.supportedUsageFlags & usageFlags) == usageFlags); - SkASSERT(caps.supportedTransforms & caps.currentTransform); - SkASSERT(caps.supportedCompositeAlpha & - (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)); - VkCompositeAlphaFlagBitsKHR composite_alpha = - (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) - ? 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; - VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; - - bool wantSRGB = false; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - wantSRGB = true; -#endif - 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 (VK_FORMAT_UNDEFINED == surfaceFormat) { - 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. - 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)); - swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - swapchainCreateInfo.surface = surface->mVkSurface; - swapchainCreateInfo.minImageCount = imageCount; - swapchainCreateInfo.imageFormat = surfaceFormat; - swapchainCreateInfo.imageColorSpace = colorSpace; - swapchainCreateInfo.imageExtent = extent; - swapchainCreateInfo.imageArrayLayers = 1; - swapchainCreateInfo.imageUsage = usageFlags; - - uint32_t queueFamilies[] = {mBackendContext->fGraphicsQueueIndex, mPresentQueueIndex}; - if (mBackendContext->fGraphicsQueueIndex != mPresentQueueIndex) { - swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - swapchainCreateInfo.queueFamilyIndexCount = 2; - swapchainCreateInfo.pQueueFamilyIndices = queueFamilies; + VkSemaphore semaphore; + VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to create semaphore"); + + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(semaphore); + + int fenceFd = -1; + DestroySemaphoreInfo* destroyInfo = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, + semaphore); + GrSemaphoresSubmitted submitted = + bufferInfo->skSurface->flush(SkSurface::BackendSurfaceAccess::kPresent, + kNone_GrFlushFlags, 1, &backendSemaphore, + destroy_semaphore, destroyInfo); + if (submitted == GrSemaphoresSubmitted::kYes) { + 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; + + err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); + ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd"); } else { - swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - swapchainCreateInfo.queueFamilyIndexCount = 0; - swapchainCreateInfo.pQueueFamilyIndices = nullptr; + ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed"); + mQueueWaitIdle(mGraphicsQueue); } - swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - swapchainCreateInfo.compositeAlpha = composite_alpha; - swapchainCreateInfo.presentMode = mode; - swapchainCreateInfo.clipped = true; - swapchainCreateInfo.oldSwapchain = surface->mSwapchain; - - res = mCreateSwapchainKHR(mBackendContext->fDevice, &swapchainCreateInfo, nullptr, - &surface->mSwapchain); - if (VK_SUCCESS != res) { - return false; - } - - // destroy the old swapchain - if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) { - mDeviceWaitIdle(mBackendContext->fDevice); - - destroyBuffers(surface); + surface->presentCurrentBuffer(dirtyRect, fenceFd); +} - mDestroySwapchainKHR(mBackendContext->fDevice, swapchainCreateInfo.oldSwapchain, nullptr); +void VulkanManager::destroySurface(VulkanSurface* surface) { + // Make sure all submit commands have finished before starting to destroy objects. + if (VK_NULL_HANDLE != mPresentQueue) { + mQueueWaitIdle(mPresentQueue); } + mDeviceWaitIdle(mDevice); - createBuffers(surface, surfaceFormat, extent); - - return true; + delete surface; } -VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) { - initialize(); - +VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode, + sk_sp<SkColorSpace> surfaceColorSpace, + SkColorType surfaceColorType, GrContext* grContext, + uint32_t extraBuffers) { + LOG_ALWAYS_FATAL_IF(!hasVkContext(), "Not initialized"); if (!window) { return nullptr; } - VulkanSurface* surface = new VulkanSurface(); - - VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; - memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); - surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; - surfaceCreateInfo.pNext = nullptr; - surfaceCreateInfo.flags = 0; - surfaceCreateInfo.window = window; + return VulkanSurface::Create(window, colorMode, surfaceColorType, surfaceColorSpace, grContext, + *this, extraBuffers); +} - VkResult res = mCreateAndroidSurfaceKHR(mBackendContext->fInstance, &surfaceCreateInfo, nullptr, - &surface->mVkSurface); - if (VK_SUCCESS != res) { - delete surface; - return nullptr; +status_t VulkanManager::fenceWait(sp<Fence>& fence, GrContext* grContext) { + if (!hasVkContext()) { + ALOGE("VulkanManager::fenceWait: VkDevice not initialized"); + return INVALID_OPERATION; } - 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);); - - if (!createSwapchain(surface)) { - destroySurface(surface); - return nullptr; + // Block GPU on the fence. + int fenceFd = fence->dup(); + if (fenceFd == -1) { + ALOGE("VulkanManager::fenceWait: error dup'ing fence fd: %d", errno); + return -errno; } - return surface; + 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) { + mDestroySemaphore(mDevice, semaphore, nullptr); + ALOGE("Failed to import semaphore, err: %d", err); + return UNKNOWN_ERROR; + } + + GrBackendSemaphore beSemaphore; + beSemaphore.initVulkan(semaphore); + + // Skia takes ownership of the semaphore and will delete it once the wait has finished. + grContext->wait(1, &beSemaphore); + grContext->flush(); + + return OK; } -// Helper to know which src stage flags we need to set when transitioning to the present layout -static VkPipelineStageFlags layoutToPipelineStageFlags(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_PREINITIALIZED == layout) { - return VK_PIPELINE_STAGE_HOST_BIT; +status_t VulkanManager::createReleaseFence(sp<Fence>& nativeFence, GrContext* grContext) { + if (!hasVkContext()) { + ALOGE("VulkanManager::createReleaseFence: VkDevice not initialized"); + return INVALID_OPERATION; } - SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout); - return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; -} + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; -// Helper to know which src access mask we need to set when transitioning to the present layout -static VkAccessFlags layoutToSrcAccessMask(const VkImageLayout layout) { - VkAccessFlags flags = 0; - if (VK_IMAGE_LAYOUT_GENERAL == layout) { - flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_HOST_WRITE_BIT | - VK_ACCESS_HOST_READ_BIT; - } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) { - flags = VK_ACCESS_HOST_WRITE_BIT; - } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) { - flags = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout) { - flags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - } else if (VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) { - flags = VK_ACCESS_TRANSFER_WRITE_BIT; - } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout) { - flags = VK_ACCESS_TRANSFER_READ_BIT; - } else if (VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) { - flags = VK_ACCESS_SHADER_READ_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; } - return flags; -} -void VulkanManager::swapBuffers(VulkanSurface* surface) { - if (CC_UNLIKELY(Properties::waitForGpuCompletion)) { - ATRACE_NAME("Finishing GPU work"); - mDeviceWaitIdle(mBackendContext->fDevice); - } + GrBackendSemaphore backendSemaphore; + backendSemaphore.initVulkan(semaphore); - 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); - // Check to make sure we never change the actually wrapped image - 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); - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - VkAccessFlags srcAccessMask = layoutToSrcAccessMask(layout); - VkAccessFlags dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - - VkImageMemoryBarrier imageMemoryBarrier = { - VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType - NULL, // pNext - srcAccessMask, // outputMask - dstAccessMask, // inputMask - layout, // oldLayout - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, // newLayout - mBackendContext->fGraphicsQueueIndex, // srcQueueFamilyIndex - mPresentQueueIndex, // dstQueueFamilyIndex - surface->mImages[backbuffer->mImageIndex], // image - {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1} // subresourceRange - }; + DestroySemaphoreInfo* destroyInfo = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, + semaphore); + GrSemaphoresSubmitted submitted = + grContext->flush(kNone_GrFlushFlags, 1, &backendSemaphore, + destroy_semaphore, destroyInfo); - mResetCommandBuffer(backbuffer->mTransitionCmdBuffers[1], 0); - VkCommandBufferBeginInfo info; - memset(&info, 0, sizeof(VkCommandBufferBeginInfo)); - info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - info.flags = 0; - mBeginCommandBuffer(backbuffer->mTransitionCmdBuffers[1], &info); - mCmdPipelineBarrier(backbuffer->mTransitionCmdBuffers[1], srcStageMask, dstStageMask, 0, 0, - nullptr, 0, nullptr, 1, &imageMemoryBarrier); - mEndCommandBuffer(backbuffer->mTransitionCmdBuffers[1]); - - surface->mImageInfos[backbuffer->mImageIndex].mImageLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - // insert the layout transfer into the queue and wait on the acquire - VkSubmitInfo submitInfo; - memset(&submitInfo, 0, sizeof(VkSubmitInfo)); - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.waitSemaphoreCount = 0; - submitInfo.pWaitDstStageMask = 0; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &backbuffer->mTransitionCmdBuffers[1]; - submitInfo.signalSemaphoreCount = 1; - // When this command buffer finishes we will signal this semaphore so that we know it is now - // safe to present the image to the screen. - 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]); - - // 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 - // queue. - const VkPresentInfoKHR presentInfo = { - VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType - NULL, // pNext - 1, // waitSemaphoreCount - &backbuffer->mRenderSemaphore, // pWaitSemaphores - 1, // swapchainCount - &surface->mSwapchain, // pSwapchains - &backbuffer->mImageIndex, // pImageIndices - NULL // pResults - }; + if (submitted == GrSemaphoresSubmitted::kNo) { + ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore"); + mDestroySemaphore(mDevice, semaphore, nullptr); + return INVALID_OPERATION; + } - mQueuePresentKHR(mPresentQueue, &presentInfo); + 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; - surface->mBackbuffer.reset(); - surface->mImageInfos[backbuffer->mImageIndex].mLastUsed = surface->mCurrentTime; - surface->mImageInfos[backbuffer->mImageIndex].mInvalid = false; - surface->mCurrentTime++; -} + int fenceFd = 0; -int VulkanManager::getAge(VulkanSurface* surface) { - SkASSERT(surface->mBackbuffers); - VulkanSurface::BackbufferInfo* backbuffer = - surface->mBackbuffers + surface->mCurrentBackbufferIndex; - if (mSwapBehavior == SwapBehavior::Discard || - surface->mImageInfos[backbuffer->mImageIndex].mInvalid) { - return 0; + err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); + if (VK_SUCCESS != err) { + ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd"); + return INVALID_OPERATION; } - uint16_t lastUsed = surface->mImageInfos[backbuffer->mImageIndex].mLastUsed; - return surface->mCurrentTime - lastUsed; + nativeFence = new Fence(fenceFd); + + return OK; } } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index ea6970469e03..dd3c6d0dba81 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -17,10 +17,22 @@ #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 <GrContextOptions.h> #include <SkSurface.h> +#include <ui/Fence.h> +#include <utils/StrongPointer.h> #include <vk/GrVkBackendContext.h> +#include <vk/GrVkExtensions.h> +#include "Frame.h" +#include "IRenderPipeline.h" +#include "VulkanSurface.h" -#include <vulkan/vulkan.h> +class GrVkExtensions; namespace android { namespace uirenderer { @@ -28,95 +40,56 @@ namespace renderthread { class RenderThread; -class VulkanSurface { -public: - VulkanSurface() {} - - sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } - -private: - friend class VulkanManager; - struct BackbufferInfo { - uint32_t mImageIndex; // image this is associated with - VkSemaphore mAcquireSemaphore; // we signal on this for acquisition of image - VkSemaphore mRenderSemaphore; // we wait on this for rendering to be done - VkCommandBuffer - mTransitionCmdBuffers[2]; // to transition layout between present and render - // We use these fences to make sure the above Command buffers have finished their work - // before attempting to reuse them or destroy them. - VkFence mUsageFences[2]; - }; - - struct ImageInfo { - VkImageLayout mImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; - sk_sp<SkSurface> mSurface; - uint16_t mLastUsed = 0; - bool mInvalid = true; - }; - - sk_sp<SkSurface> mBackbuffer; - - VkSurfaceKHR mVkSurface = VK_NULL_HANDLE; - VkSwapchainKHR mSwapchain = VK_NULL_HANDLE; - - BackbufferInfo* mBackbuffers = nullptr; - uint32_t mCurrentBackbufferIndex; - - uint32_t mImageCount; - VkImage* mImages = nullptr; - ImageInfo* mImageInfos; - uint16_t mCurrentTime = 0; -}; - // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue, // which are re-used by CanvasContext. This class is created once and should be used by all vulkan // windowing contexts. The VulkanManager must be initialized before use. class VulkanManager { public: + explicit VulkanManager() {} + ~VulkanManager() { destroy(); } + // Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must // be call once before use of the VulkanManager. Multiple calls after the first will simiply // return. 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); - - // Destroy the VulkanSurface and all associated vulkan objects. + // Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface + VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode, + sk_sp<SkColorSpace> surfaceColorSpace, + SkColorType surfaceColorType, GrContext* grContext, + uint32_t extraBuffers); void destroySurface(VulkanSurface* surface); + Frame dequeueNextBuffer(VulkanSurface* surface); + void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect); + // Cleans up all the global state in the VulkanManger. void destroy(); - // No work is needed to make a VulkanSurface current, and all functions require that a - // VulkanSurface is passed into them so we just return true here. - bool isCurrent(VulkanSurface* surface) { return true; } + // Inserts a wait on fence command into the Vulkan command buffer. + status_t fenceWait(sp<Fence>& fence, GrContext* grContext); - int getAge(VulkanSurface* surface); - - // Returns an SkSurface which wraps the next image returned from vkAcquireNextImageKHR. It also - // will transition the VkImage from a present layout to color attachment so that it can be used - // by the client for drawing. - SkSurface* getBackbufferSurface(VulkanSurface* surface); - - // Presents the current VkImage. - void swapBuffers(VulkanSurface* surface); - -private: - friend class RenderThread; + // Creates a fence that is signaled when all the pending Vulkan commands are finished on the + // GPU. + status_t createReleaseFence(sp<Fence>& nativeFence, GrContext* grContext); - explicit VulkanManager(RenderThread& thread); - ~VulkanManager() { destroy(); } + // Returned pointers are owned by VulkanManager. + // An instance of VkFunctorInitParams returned from getVkFunctorInitParams refers to + // the internal state of VulkanManager: VulkanManager must be alive to use the returned value. + VkFunctorInitParams getVkFunctorInitParams() const; - void destroyBuffers(VulkanSurface* surface); + sk_sp<GrContext> createContext(const GrContextOptions& options); - bool createSwapchain(VulkanSurface* surface); - void createBuffers(VulkanSurface* surface, VkFormat format, VkExtent2D extent); + uint32_t getDriverVersion() const { return mDriverVersion; } - VulkanSurface::BackbufferInfo* getAvailableBackbuffer(VulkanSurface* surface); +private: + friend class VulkanSurface; + // Sets up the VkInstance and VkDevice objects. Also fills out the passed in + // VkPhysicalDeviceFeatures struct. + void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&); // simple wrapper class that exists only to initialize a pointer to NULL template <typename FNPTR_TYPE> @@ -142,14 +115,24 @@ private: VkPtr<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR> mGetPhysicalDeviceSurfaceFormatsKHR; VkPtr<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR> mGetPhysicalDeviceSurfacePresentModesKHR; - VkPtr<PFN_vkCreateSwapchainKHR> mCreateSwapchainKHR; - VkPtr<PFN_vkDestroySwapchainKHR> mDestroySwapchainKHR; - VkPtr<PFN_vkGetSwapchainImagesKHR> mGetSwapchainImagesKHR; - VkPtr<PFN_vkAcquireNextImageKHR> mAcquireNextImageKHR; - 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_vkGetPhysicalDeviceImageFormatProperties2> mGetPhysicalDeviceImageFormatProperties2; + 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; @@ -159,30 +142,43 @@ 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; VkPtr<PFN_vkResetFences> mResetFences; - RenderThread& mRenderThread; + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice = VK_NULL_HANDLE; - sk_sp<const GrVkBackendContext> mBackendContext; + uint32_t mGraphicsQueueIndex; + VkQueue mGraphicsQueue = VK_NULL_HANDLE; uint32_t mPresentQueueIndex; VkQueue mPresentQueue = VK_NULL_HANDLE; VkCommandPool mCommandPool = VK_NULL_HANDLE; + // Variables saved to populate VkFunctorInitParams. + static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0); + std::vector<VkExtensionProperties> mInstanceExtensionsOwner; + std::vector<const char*> mInstanceExtensions; + std::vector<VkExtensionProperties> mDeviceExtensionsOwner; + std::vector<const char*> mDeviceExtensions; + VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures2{}; + enum class SwapBehavior { Discard, BufferAge, }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; + GrVkExtensions mExtensions; + uint32_t mDriverVersion = 0; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp new file mode 100644 index 000000000000..b2cc23e76b8a --- /dev/null +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "VulkanSurface.h" + +#include <SkSurface.h> +#include <algorithm> + +#include "VulkanManager.h" +#include "utils/Color.h" +#include "utils/TraceUtils.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +static bool IsTransformSupported(int transform) { + // For now, only support pure rotations, not flip or flip-and-rotate, until we have + // more time to test them and build sample code. As far as I know we never actually + // use anything besides pure rotations anyway. + return transform == 0 || transform == NATIVE_WINDOW_TRANSFORM_ROT_90 || + transform == NATIVE_WINDOW_TRANSFORM_ROT_180 || + transform == NATIVE_WINDOW_TRANSFORM_ROT_270; +} + +static int InvertTransform(int transform) { + switch (transform) { + case NATIVE_WINDOW_TRANSFORM_ROT_90: + return NATIVE_WINDOW_TRANSFORM_ROT_270; + case NATIVE_WINDOW_TRANSFORM_ROT_180: + return NATIVE_WINDOW_TRANSFORM_ROT_180; + case NATIVE_WINDOW_TRANSFORM_ROT_270: + return NATIVE_WINDOW_TRANSFORM_ROT_90; + default: + return 0; + } +} + +static int ConvertVkTransformToNative(VkSurfaceTransformFlagsKHR transform) { + switch (transform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: + return NATIVE_WINDOW_TRANSFORM_ROT_270; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: + return NATIVE_WINDOW_TRANSFORM_ROT_180; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: + return NATIVE_WINDOW_TRANSFORM_ROT_90; + case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: + case VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR: + default: + return 0; + } +} + +static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) { + const int width = windowSize.width(); + const int height = windowSize.height(); + + switch (transform) { + case 0: + return SkMatrix::I(); + case NATIVE_WINDOW_TRANSFORM_ROT_90: + return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1); + case NATIVE_WINDOW_TRANSFORM_ROT_180: + return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1); + case NATIVE_WINDOW_TRANSFORM_ROT_270: + return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1); + default: + LOG_ALWAYS_FATAL("Unsupported Window Transform (%d)", transform); + } + return SkMatrix::I(); +} + +void VulkanSurface::ComputeWindowSizeAndTransform(WindowInfo* windowInfo, const SkISize& minSize, + const SkISize& maxSize) { + SkISize& windowSize = windowInfo->size; + + // clamp width & height to handle currentExtent of -1 and protect us from broken hints + if (windowSize.width() < minSize.width() || windowSize.width() > maxSize.width() || + windowSize.height() < minSize.height() || windowSize.height() > maxSize.height()) { + int width = std::min(maxSize.width(), std::max(minSize.width(), windowSize.width())); + int height = std::min(maxSize.height(), std::max(minSize.height(), windowSize.height())); + ALOGE("Invalid Window Dimensions [%d, %d]; clamping to [%d, %d]", windowSize.width(), + windowSize.height(), width, height); + windowSize.set(width, height); + } + + windowInfo->actualSize = windowSize; + if (windowInfo->transform & HAL_TRANSFORM_ROT_90) { + windowInfo->actualSize.set(windowSize.height(), windowSize.width()); + } + + windowInfo->preTransform = GetPreTransformMatrix(windowInfo->size, windowInfo->transform); +} + +static bool ResetNativeWindow(ANativeWindow* window) { + // -- Reset the native window -- + // The native window might have been used previously, and had its properties + // changed from defaults. That will affect the answer we get for queries + // like MIN_UNDEQUEUED_BUFFERS. Reset to a known/default state before we + // attempt such queries. + + int err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL); + if (err != 0) { + ALOGW("native_window_api_connect failed: %s (%d)", strerror(-err), err); + return false; + } + + // this will match what we do on GL so pick that here. + err = window->setSwapInterval(window, 1); + if (err != 0) { + ALOGW("native_window->setSwapInterval(1) failed: %s (%d)", strerror(-err), err); + return false; + } + + err = native_window_set_shared_buffer_mode(window, false); + if (err != 0) { + ALOGW("native_window_set_shared_buffer_mode(false) failed: %s (%d)", strerror(-err), err); + return false; + } + + err = native_window_set_auto_refresh(window, false); + if (err != 0) { + ALOGW("native_window_set_auto_refresh(false) failed: %s (%d)", strerror(-err), err); + return false; + } + + return true; +} + +class VkSurfaceAutoDeleter { +public: + VkSurfaceAutoDeleter(VkInstance instance, VkSurfaceKHR surface, + PFN_vkDestroySurfaceKHR destroySurfaceKHR) + : mInstance(instance), mSurface(surface), mDestroySurfaceKHR(destroySurfaceKHR) {} + ~VkSurfaceAutoDeleter() { destroy(); } + + void destroy() { + if (mSurface != VK_NULL_HANDLE) { + mDestroySurfaceKHR(mInstance, mSurface, nullptr); + mSurface = VK_NULL_HANDLE; + } + } + +private: + VkInstance mInstance; + VkSurfaceKHR mSurface; + PFN_vkDestroySurfaceKHR mDestroySurfaceKHR; +}; + +VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode, + SkColorType colorType, sk_sp<SkColorSpace> colorSpace, + GrContext* grContext, const VulkanManager& vkManager, + uint32_t extraBuffers) { + VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; + memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + surfaceCreateInfo.pNext = nullptr; + surfaceCreateInfo.flags = 0; + surfaceCreateInfo.window = window; + + VkSurfaceKHR vkSurface = VK_NULL_HANDLE; + VkResult res = vkManager.mCreateAndroidSurfaceKHR(vkManager.mInstance, &surfaceCreateInfo, + nullptr, &vkSurface); + if (VK_SUCCESS != res) { + ALOGE("VulkanSurface::Create() vkCreateAndroidSurfaceKHR failed (%d)", res); + return nullptr; + } + + VkSurfaceAutoDeleter vkSurfaceDeleter(vkManager.mInstance, vkSurface, + vkManager.mDestroySurfaceKHR); + + SkDEBUGCODE(VkBool32 supported; res = vkManager.mGetPhysicalDeviceSurfaceSupportKHR( + vkManager.mPhysicalDevice, vkManager.mPresentQueueIndex, + vkSurface, &supported); + // All physical devices and queue families on Android must be capable of + // presentation with any native window. + SkASSERT(VK_SUCCESS == res && supported);); + + // check for capabilities + VkSurfaceCapabilitiesKHR caps; + res = vkManager.mGetPhysicalDeviceSurfaceCapabilitiesKHR(vkManager.mPhysicalDevice, vkSurface, + &caps); + if (VK_SUCCESS != res) { + ALOGE("VulkanSurface::Create() vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed (%d)", res); + return nullptr; + } + + LOG_ALWAYS_FATAL_IF(0 == (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)); + + /* + * We must destroy the VK Surface before attempting to update the window as doing so after + * will cause the native window to be modified in unexpected ways. + */ + vkSurfaceDeleter.destroy(); + + /* + * Populate Window Info struct + */ + WindowInfo windowInfo; + + windowInfo.transform = ConvertVkTransformToNative(caps.supportedTransforms); + windowInfo.size = SkISize::Make(caps.currentExtent.width, caps.currentExtent.height); + + const SkISize minSize = SkISize::Make(caps.minImageExtent.width, caps.minImageExtent.height); + const SkISize maxSize = SkISize::Make(caps.maxImageExtent.width, caps.maxImageExtent.height); + ComputeWindowSizeAndTransform(&windowInfo, minSize, maxSize); + + int query_value; + int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); + if (err != 0 || query_value < 0) { + ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value); + return nullptr; + } + auto min_undequeued_buffers = static_cast<uint32_t>(query_value); + + windowInfo.bufferCount = min_undequeued_buffers + + std::max(sTargetBufferCount + extraBuffers, caps.minImageCount); + if (caps.maxImageCount > 0 && windowInfo.bufferCount > caps.maxImageCount) { + // Application must settle for fewer images than desired: + windowInfo.bufferCount = caps.maxImageCount; + } + + // Currently Skia requires the images to be color attachments and support all transfer + // operations. + VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + LOG_ALWAYS_FATAL_IF((caps.supportedUsageFlags & usageFlags) != usageFlags); + + windowInfo.dataspace = HAL_DATASPACE_V0_SRGB; + if (colorMode == ColorMode::WideColorGamut) { + skcms_Matrix3x3 surfaceGamut; + LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&surfaceGamut), + "Could not get gamut matrix from color space"); + if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) { + windowInfo.dataspace = HAL_DATASPACE_V0_SCRGB; + } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) { + windowInfo.dataspace = HAL_DATASPACE_DISPLAY_P3; + } else { + LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space."); + } + } + + windowInfo.pixelFormat = ColorTypeToPixelFormat(colorType); + VkFormat vkPixelFormat = VK_FORMAT_R8G8B8A8_UNORM; + if (windowInfo.pixelFormat == PIXEL_FORMAT_RGBA_FP16) { + vkPixelFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + } + + LOG_ALWAYS_FATAL_IF(nullptr == vkManager.mGetPhysicalDeviceImageFormatProperties2, + "vkGetPhysicalDeviceImageFormatProperties2 is missing"); + VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo; + externalImageFormatInfo.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO; + externalImageFormatInfo.pNext = nullptr; + externalImageFormatInfo.handleType = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; + + VkPhysicalDeviceImageFormatInfo2 imageFormatInfo; + imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; + imageFormatInfo.pNext = &externalImageFormatInfo; + imageFormatInfo.format = vkPixelFormat; + imageFormatInfo.type = VK_IMAGE_TYPE_2D; + imageFormatInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageFormatInfo.usage = usageFlags; + imageFormatInfo.flags = 0; + + VkAndroidHardwareBufferUsageANDROID hwbUsage; + hwbUsage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID; + hwbUsage.pNext = nullptr; + + VkImageFormatProperties2 imgFormProps; + imgFormProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; + imgFormProps.pNext = &hwbUsage; + + res = vkManager.mGetPhysicalDeviceImageFormatProperties2(vkManager.mPhysicalDevice, + &imageFormatInfo, &imgFormProps); + if (VK_SUCCESS != res) { + ALOGE("Failed to query GetPhysicalDeviceImageFormatProperties2"); + return nullptr; + } + + uint64_t consumerUsage; + native_window_get_consumer_usage(window, &consumerUsage); + windowInfo.windowUsageFlags = consumerUsage | hwbUsage.androidHardwareBufferUsage; + + /* + * Now we attempt to modify the window! + */ + if (!UpdateWindow(window, windowInfo)) { + return nullptr; + } + + return new VulkanSurface(window, windowInfo, minSize, maxSize, grContext); +} + +bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo) { + ATRACE_CALL(); + + if (!ResetNativeWindow(window)) { + return false; + } + + // -- Configure the native window -- + int err = native_window_set_buffers_format(window, windowInfo.pixelFormat); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_format(%d) failed: %s (%d)", + windowInfo.pixelFormat, strerror(-err), err); + return false; + } + + err = native_window_set_buffers_data_space(window, windowInfo.dataspace); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_data_space(%d) " + "failed: %s (%d)", + windowInfo.dataspace, strerror(-err), err); + return false; + } + + const SkISize& size = windowInfo.actualSize; + err = native_window_set_buffers_dimensions(window, size.width(), size.height()); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_dimensions(%d,%d) " + "failed: %s (%d)", + size.width(), size.height(), strerror(-err), err); + return false; + } + + // native_window_set_buffers_transform() expects the transform the app is requesting that + // the compositor perform during composition. With native windows, pre-transform works by + // rendering with the same transform the compositor is applying (as in Vulkan), but + // then requesting the inverse transform, so that when the compositor does + // it's job the two transforms cancel each other out and the compositor ends + // up applying an identity transform to the app's buffer. + err = native_window_set_buffers_transform(window, InvertTransform(windowInfo.transform)); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_transform(%d) " + "failed: %s (%d)", + windowInfo.transform, strerror(-err), err); + return false; + } + + // Vulkan defaults to NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW, but this is different than + // HWUI's expectation + err = native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_FREEZE); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_scaling_mode(SCALE_TO_WINDOW) " + "failed: %s (%d)", + strerror(-err), err); + return false; + } + + err = native_window_set_buffer_count(window, windowInfo.bufferCount); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s (%d)", + windowInfo.bufferCount, strerror(-err), err); + return false; + } + + err = native_window_set_usage(window, windowInfo.windowUsageFlags); + if (err != 0) { + ALOGE("VulkanSurface::UpdateWindow() native_window_set_usage failed: %s (%d)", + strerror(-err), err); + return false; + } + + return err == 0; +} + +VulkanSurface::VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, + SkISize minWindowSize, SkISize maxWindowSize, GrContext* grContext) + : mNativeWindow(window) + , mWindowInfo(windowInfo) + , mGrContext(grContext) + , mMinWindowSize(minWindowSize) + , mMaxWindowSize(maxWindowSize) {} + +VulkanSurface::~VulkanSurface() { + releaseBuffers(); + + // release the native window to be available for use by other clients + int err = native_window_api_disconnect(mNativeWindow.get(), NATIVE_WINDOW_API_EGL); + ALOGW_IF(err != 0, "native_window_api_disconnect failed: %s (%d)", strerror(-err), err); +} + +void VulkanSurface::releaseBuffers() { + for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) { + VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i]; + + if (bufferInfo.buffer.get() != nullptr && bufferInfo.dequeued) { + int err = mNativeWindow->cancelBuffer(mNativeWindow.get(), bufferInfo.buffer.get(), + bufferInfo.dequeue_fence); + if (err != 0) { + ALOGE("cancelBuffer[%u] failed during destroy: %s (%d)", i, strerror(-err), err); + } + bufferInfo.dequeued = false; + + if (bufferInfo.dequeue_fence >= 0) { + close(bufferInfo.dequeue_fence); + bufferInfo.dequeue_fence = -1; + } + } + + LOG_ALWAYS_FATAL_IF(bufferInfo.dequeued); + LOG_ALWAYS_FATAL_IF(bufferInfo.dequeue_fence != -1); + + bufferInfo.skSurface.reset(); + bufferInfo.buffer.clear(); + bufferInfo.hasValidContents = false; + bufferInfo.lastPresentedCount = 0; + } +} + +VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { + // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct + // value at the end of the function if everything dequeued correctly. + mCurrentBufferInfo = nullptr; + + // check if the native window has been resized or rotated and update accordingly + SkISize newSize = SkISize::MakeEmpty(); + int transformHint = 0; + mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &newSize.fWidth); + mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &newSize.fHeight); + mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_TRANSFORM_HINT, &transformHint); + if (newSize != mWindowInfo.actualSize || transformHint != mWindowInfo.transform) { + WindowInfo newWindowInfo = mWindowInfo; + newWindowInfo.size = newSize; + newWindowInfo.transform = IsTransformSupported(transformHint) ? transformHint : 0; + ComputeWindowSizeAndTransform(&newWindowInfo, mMinWindowSize, mMaxWindowSize); + + int err = 0; + if (newWindowInfo.actualSize != mWindowInfo.actualSize) { + // reset the native buffers and update the window + err = native_window_set_buffers_dimensions(mNativeWindow.get(), + newWindowInfo.actualSize.width(), + newWindowInfo.actualSize.height()); + if (err != 0) { + ALOGE("native_window_set_buffers_dimensions(%d,%d) failed: %s (%d)", + newWindowInfo.actualSize.width(), newWindowInfo.actualSize.height(), + strerror(-err), err); + return nullptr; + } + // reset the NativeBufferInfo (including SkSurface) associated with the old buffers. The + // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer. + releaseBuffers(); + // TODO should we ask the nativewindow to allocate buffers? + } + + if (newWindowInfo.transform != mWindowInfo.transform) { + err = native_window_set_buffers_transform(mNativeWindow.get(), + InvertTransform(newWindowInfo.transform)); + if (err != 0) { + ALOGE("native_window_set_buffers_transform(%d) failed: %s (%d)", + newWindowInfo.transform, strerror(-err), err); + newWindowInfo.transform = mWindowInfo.transform; + ComputeWindowSizeAndTransform(&newWindowInfo, mMinWindowSize, mMaxWindowSize); + } + } + + mWindowInfo = newWindowInfo; + } + + ANativeWindowBuffer* buffer; + int fence_fd; + int err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buffer, &fence_fd); + if (err != 0) { + ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err); + return nullptr; + } + + uint32_t idx; + for (idx = 0; idx < mWindowInfo.bufferCount; idx++) { + if (mNativeBuffers[idx].buffer.get() == buffer) { + mNativeBuffers[idx].dequeued = true; + mNativeBuffers[idx].dequeue_fence = fence_fd; + break; + } else if (mNativeBuffers[idx].buffer.get() == nullptr) { + // increasing the number of buffers we have allocated + mNativeBuffers[idx].buffer = buffer; + mNativeBuffers[idx].dequeued = true; + mNativeBuffers[idx].dequeue_fence = fence_fd; + break; + } + } + if (idx == mWindowInfo.bufferCount) { + ALOGE("dequeueBuffer returned unrecognized buffer"); + mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd); + return nullptr; + } + + VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx]; + + if (bufferInfo->skSurface.get() == nullptr) { + bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( + mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), + kTopLeft_GrSurfaceOrigin, DataSpaceToColorSpace(mWindowInfo.dataspace), nullptr); + if (bufferInfo->skSurface.get() == nullptr) { + ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); + mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd); + return nullptr; + } + } + + mCurrentBufferInfo = bufferInfo; + return bufferInfo; +} + +bool VulkanSurface::presentCurrentBuffer(const SkRect& dirtyRect, int semaphoreFd) { + if (!dirtyRect.isEmpty()) { + + // native_window_set_surface_damage takes a rectangle in prerotated space + // with a bottom-left origin. That is, top > bottom. + // The dirtyRect is also in prerotated space, so we just need to switch it to + // a bottom-left origin space. + + SkIRect irect; + dirtyRect.roundOut(&irect); + android_native_rect_t aRect; + aRect.left = irect.left(); + aRect.top = logicalHeight() - irect.top(); + aRect.right = irect.right(); + aRect.bottom = logicalHeight() - irect.bottom(); + + int err = native_window_set_surface_damage(mNativeWindow.get(), &aRect, 1); + ALOGE_IF(err != 0, "native_window_set_surface_damage failed: %s (%d)", strerror(-err), err); + } + + LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo); + VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo; + int queuedFd = (semaphoreFd != -1) ? semaphoreFd : currentBuffer.dequeue_fence; + int err = mNativeWindow->queueBuffer(mNativeWindow.get(), currentBuffer.buffer.get(), queuedFd); + + currentBuffer.dequeued = false; + // queueBuffer always closes fence, even on error + if (err != 0) { + ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err); + mNativeWindow->cancelBuffer(mNativeWindow.get(), currentBuffer.buffer.get(), + currentBuffer.dequeue_fence); + } else { + currentBuffer.hasValidContents = true; + currentBuffer.lastPresentedCount = mPresentCount; + mPresentCount++; + } + + if (currentBuffer.dequeue_fence >= 0) { + close(currentBuffer.dequeue_fence); + currentBuffer.dequeue_fence = -1; + } + + return err == 0; +} + +int VulkanSurface::getCurrentBuffersAge() { + LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo); + VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo; + return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0; +} + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h new file mode 100644 index 000000000000..b7af596ae762 --- /dev/null +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 <system/graphics.h> +#include <system/window.h> +#include <vulkan/vulkan.h> + +#include <SkRefCnt.h> +#include <SkSize.h> + +#include "IRenderPipeline.h" + +class SkSurface; + +namespace android { +namespace uirenderer { +namespace renderthread { + +class VulkanManager; + +class VulkanSurface { +public: + static VulkanSurface* Create(ANativeWindow* window, ColorMode colorMode, SkColorType colorType, + sk_sp<SkColorSpace> colorSpace, GrContext* grContext, + const VulkanManager& vkManager, uint32_t extraBuffers); + ~VulkanSurface(); + + sk_sp<SkSurface> getCurrentSkSurface() { + return mCurrentBufferInfo ? mCurrentBufferInfo->skSurface : nullptr; + } + const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; } + +private: + /* + * All structs/methods in this private section are specifically for use by the VulkanManager + * + */ + friend VulkanManager; + struct NativeBufferInfo { + sk_sp<SkSurface> skSurface; + sp<ANativeWindowBuffer> buffer; + // The fence is only valid when the buffer is dequeued, and should be + // -1 any other time. When valid, we own the fd, and must ensure it is + // closed: either by closing it explicitly when queueing the buffer, + // or by passing ownership e.g. to ANativeWindow::cancelBuffer(). + int dequeue_fence = -1; + bool dequeued = false; + uint32_t lastPresentedCount = 0; + bool hasValidContents = false; + }; + + NativeBufferInfo* dequeueNativeBuffer(); + NativeBufferInfo* getCurrentBufferInfo() { return mCurrentBufferInfo; } + bool presentCurrentBuffer(const SkRect& dirtyRect, int semaphoreFd); + + // The width and height are are the logical width and height for when submitting draws to the + // surface. In reality if the window is rotated the underlying window may have the width and + // height swapped. + int logicalWidth() const { return mWindowInfo.size.width(); } + int logicalHeight() const { return mWindowInfo.size.height(); } + int getCurrentBuffersAge(); + +private: + /* + * All code below this line while logically available to VulkanManager should not be treated + * as private to this class. + * + */ + + // How many buffers we want to be able to use ourselves. We want 1 in active rendering with + // 1 more queued, so 2. This will be added to NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, which is + // how many buffers the consumer needs (eg, 1 for SurfaceFlinger), getting to a typically + // triple-buffered queue as a result. + static constexpr uint32_t sTargetBufferCount = 2; + + struct WindowInfo { + SkISize size; + PixelFormat pixelFormat; + android_dataspace dataspace; + int transform; + size_t bufferCount; + uint64_t windowUsageFlags; + + // size of the ANativeWindow if the inverse of transform requires us to swap width/height + SkISize actualSize; + // transform to be applied to the SkSurface to map the coordinates to the provided transform + SkMatrix preTransform; + }; + + VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, SkISize minWindowSize, + SkISize maxWindowSize, GrContext* grContext); + static bool UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo); + static void ComputeWindowSizeAndTransform(WindowInfo* windowInfo, const SkISize& minSize, + const SkISize& maxSize); + void releaseBuffers(); + + // TODO: Just use a vector? + NativeBufferInfo mNativeBuffers[android::BufferQueueDefs::NUM_BUFFER_SLOTS]; + + sp<ANativeWindow> mNativeWindow; + WindowInfo mWindowInfo; + GrContext* mGrContext; + + uint32_t mPresentCount = 0; + NativeBufferInfo* mCurrentBufferInfo = nullptr; + + const SkISize mMinWindowSize; + const SkISize mMaxWindowSize; +}; + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/surfacetexture/EGLConsumer.cpp b/libs/hwui/surfacetexture/EGLConsumer.cpp new file mode 100644 index 000000000000..85b3917809fa --- /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..7dac3ef0f44a --- /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..bae616bbc636 --- /dev/null +++ b/libs/hwui/surfacetexture/ImageConsumer.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 "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" +#include "utils/Color.h" +#include <GrAHardwareBufferUtils.h> +#include <GrBackendSurface.h> + +// Macro for including the SurfaceTexture name in log messages +#define IMG_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__) + +using namespace android::uirenderer::renderthread; + +namespace android { + +void ImageConsumer::onFreeBufferLocked(int slotIndex) { + // This callback may be invoked on any thread. + mImageSlots[slotIndex].clear(); +} + +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].clear(); + } +} + +void ImageConsumer::onReleaseBufferLocked(int buf) { + mImageSlots[buf].eglFence() = EGL_NO_SYNC_KHR; +} + +/** + * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object + * that keeps GPU resources alive until the last SKImage object using them is destroyed. + */ +class AutoBackendTextureRelease { +public: + static void releaseProc(SkImage::ReleaseContext releaseContext); + + AutoBackendTextureRelease(GrContext* context, GraphicBuffer* buffer); + + const GrBackendTexture& getTexture() const { return mBackendTexture; } + + void ref() { mUsageCount++; } + + void unref(bool releaseImage); + + inline sk_sp<SkImage> getImage() { return mImage; } + + void makeImage(sp<GraphicBuffer>& graphicBuffer, android_dataspace dataspace, + GrContext* context); + +private: + // The only way to invoke dtor is with unref, when mUsageCount is 0. + ~AutoBackendTextureRelease() {} + + GrBackendTexture mBackendTexture; + GrAHardwareBufferUtils::DeleteImageProc mDeleteProc; + GrAHardwareBufferUtils::DeleteImageCtx mDeleteCtx; + + // Starting with refcount 1, because the first ref is held by SurfaceTexture. Additional refs + // are held by SkImages. + int mUsageCount = 1; + + // mImage is the SkImage created from mBackendTexture. + sk_sp<SkImage> mImage; +}; + +AutoBackendTextureRelease::AutoBackendTextureRelease(GrContext* context, GraphicBuffer* buffer) { + bool createProtectedImage = + 0 != (buffer->getUsage() & GraphicBuffer::USAGE_PROTECTED); + GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat( + context, + reinterpret_cast<AHardwareBuffer*>(buffer), + buffer->getPixelFormat(), + false); + mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture( + context, + reinterpret_cast<AHardwareBuffer*>(buffer), + buffer->getWidth(), + buffer->getHeight(), + &mDeleteProc, + &mDeleteCtx, + createProtectedImage, + backendFormat, + false); +} + +void AutoBackendTextureRelease::unref(bool releaseImage) { + if (!RenderThread::isCurrent()) { + // EGLImage needs to be destroyed on RenderThread to prevent memory leak. + // ~SkImage dtor for both pipelines needs to be invoked on RenderThread, because it is not + // thread safe. + RenderThread::getInstance().queue().post([this, releaseImage]() { unref(releaseImage); }); + return; + } + + if (releaseImage) { + mImage.reset(); + } + + mUsageCount--; + if (mUsageCount <= 0) { + if (mBackendTexture.isValid()) { + mDeleteProc(mDeleteCtx); + mBackendTexture = {}; + } + delete this; + } +} + +void AutoBackendTextureRelease::releaseProc(SkImage::ReleaseContext releaseContext) { + AutoBackendTextureRelease* textureRelease = + reinterpret_cast<AutoBackendTextureRelease*>(releaseContext); + textureRelease->unref(false); +} + +void AutoBackendTextureRelease::makeImage(sp<GraphicBuffer>& graphicBuffer, + android_dataspace dataspace, GrContext* context) { + SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat( + graphicBuffer->getPixelFormat()); + mImage = SkImage::MakeFromTexture(context, + mBackendTexture, + kTopLeft_GrSurfaceOrigin, + colorType, + kPremul_SkAlphaType, + uirenderer::DataSpaceToColorSpace(dataspace), + releaseProc, + this); + if (mImage.get()) { + // The following ref will be counteracted by releaseProc, when SkImage is discarded. + ref(); + } +} + +void ImageConsumer::ImageSlot::createIfNeeded(sp<GraphicBuffer> graphicBuffer, + android_dataspace dataspace, bool forceCreate, + GrContext* context) { + if (!mTextureRelease || !mTextureRelease->getImage().get() || dataspace != mDataspace + || forceCreate) { + if (!graphicBuffer.get()) { + clear(); + return; + } + + if (!mTextureRelease) { + mTextureRelease = new AutoBackendTextureRelease(context, graphicBuffer.get()); + } + + mDataspace = dataspace; + mTextureRelease->makeImage(graphicBuffer, dataspace, context); + } +} + +void ImageConsumer::ImageSlot::clear() { + if (mTextureRelease) { + // The following unref counteracts the initial mUsageCount of 1, set by default initializer. + mTextureRelease->unref(true); + mTextureRelease = nullptr; + } +} + +sk_sp<SkImage> ImageConsumer::ImageSlot::getImage() { + return mTextureRelease ? mTextureRelease->getImage() : 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, + st.mCurrentDataSpace, false, renderState.getRenderThread().getGrContext()); + return mImageSlots[slot].getImage(); + } + } + 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, renderState.getRenderThread().getGrContext()); + } + 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].eglFence(), + releaseFence); + } else { + err = renderState.getRenderThread().vulkanManager().createReleaseFence( + releaseFence, renderState.getRenderThread().getGrContext()); + } + 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].eglFence()); + 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, item.mDataSpace, true, + renderState.getRenderThread().getGrContext()); + return mImageSlots[slot].getImage(); +} + +} /* namespace android */ diff --git a/libs/hwui/surfacetexture/ImageConsumer.h b/libs/hwui/surfacetexture/ImageConsumer.h new file mode 100644 index 000000000000..2fdece989876 --- /dev/null +++ b/libs/hwui/surfacetexture/ImageConsumer.h @@ -0,0 +1,120 @@ +/* + * 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 GrAHardwareBufferUtils { +typedef void* DeleteImageCtx; +typedef void (*DeleteImageProc)(DeleteImageCtx); +} + +namespace android { + +namespace uirenderer { +class RenderState; +} + +class AutoBackendTextureRelease; +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. + */ + class ImageSlot { + public: + ImageSlot() : mDataspace(HAL_DATASPACE_UNKNOWN), mEglFence(EGL_NO_SYNC_KHR) {} + + ~ImageSlot() { clear(); } + + void createIfNeeded(sp<GraphicBuffer> graphicBuffer, android_dataspace dataspace, + bool forceCreate, GrContext* context); + + void clear(); + + inline EGLSyncKHR& eglFence() { return mEglFence; } + + sk_sp<SkImage> getImage(); + + private: + // the dataspace associated with the current image + android_dataspace mDataspace; + + /** + * mEglFence is the EGL sync object that must signal before the buffer + * associated with this buffer slot may be dequeued. + */ + EGLSyncKHR mEglFence; + + /** + * mTextureRelease may outlive ImageConsumer, if the last ref is held by an SkImage. + * ImageConsumer holds one ref to mTextureRelease, which is decremented by "clear". + */ + AutoBackendTextureRelease* mTextureRelease = nullptr; + }; + + /** + * 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..a27db6591d6a --- /dev/null +++ b/libs/hwui/surfacetexture/SurfaceTexture.cpp @@ -0,0 +1,499 @@ +/* + * 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" +#include "ImageConsumer.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/Vulkan 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; + // Free all EglImage and VkImage before the context is destroyed. + for (int i=0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) { + mImageConsumer.onFreeBufferLocked(i); + } + } 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, 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); + } + return image; +} + +} // namespace android diff --git a/libs/hwui/surfacetexture/SurfaceTexture.h b/libs/hwui/surfacetexture/SurfaceTexture.h new file mode 100644 index 000000000000..b5d136ff3058 --- /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, 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/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 92b6cbdfc613..0a54aca4970d 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -37,11 +37,13 @@ static android::DisplayInfo DUMMY_DISPLAY{ 0, // presentationDeadline }; -DisplayInfo getBuiltInDisplay() { +DisplayInfo getInternalDisplay() { #if !HWUI_NULL_GPU DisplayInfo display; - sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain)); - status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display); + const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken(); + LOG_ALWAYS_FATAL_IF(token == nullptr, + "Failed to get display info because internal display is disconnected\n"); + status_t status = SurfaceComposerClient::getDisplayInfo(token, &display); LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n"); return display; #else diff --git a/libs/hwui/tests/common/TestContext.h b/libs/hwui/tests/common/TestContext.h index 0996f4dc706e..116d4de8090a 100644 --- a/libs/hwui/tests/common/TestContext.h +++ b/libs/hwui/tests/common/TestContext.h @@ -36,7 +36,7 @@ namespace test { extern DisplayInfo gDisplay; #define dp(x) ((x)*android::uirenderer::test::gDisplay.density) -DisplayInfo getBuiltInDisplay(); +DisplayInfo getInternalDisplay(); class TestContext { public: diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h index 91022cfe734b..74a039b3d090 100644 --- a/libs/hwui/tests/common/TestScene.h +++ b/libs/hwui/tests/common/TestScene.h @@ -38,6 +38,7 @@ public: int count = 0; int reportFrametimeWeight = 0; bool renderOffscreen = true; + int renderAhead = 0; }; template <class T> diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 02ac97e0ed5c..e8ba15fe92af 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -19,20 +19,21 @@ #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 { +std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{}; + SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { int startA = (start >> 24) & 0xff; int startR = (start >> 16) & 0xff; @@ -53,9 +54,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,90 +69,45 @@ 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); - } + layerUpdater->updateLayer(true, SkMatrix::I(), 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, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); - SkPaint glyphPaint(paint); - glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, - glyphPaint, nullptr, nullptr /* measured text */); + uint32_t length = strlen(text); + + canvas->drawText(utf16.get(), length, // text buffer + 0, length, // draw range + 0, length, // context range + x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */); } -void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path) { auto utf16 = asciiToUtf16(text); - SkPaint glyphPaint(paint); - glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint, - nullptr); + canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint, + nullptr); } void TestUtils::TestTask::run() { // RenderState only valid once RenderThread is running, so queried here renderthread::RenderThread& renderThread = renderthread::RenderThread::getInstance(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - renderThread.vulkanManager().initialize(); + renderThread.requireVkContext(); } else { - renderThread.eglManager().initialize(); + renderThread.requireGlContext(); } rtCallback(renderThread); - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - renderThread.vulkanManager().destroy(); - } else { - renderThread.renderState().flush(Caches::FlushMode::Full); - renderThread.eglManager().destroy(); - } + renderThread.destroyRenderingContext(); } std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) { diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 2752ae9a8036..e7124df72beb 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -16,21 +16,18 @@ #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 <gtest/gtest.h> #include <memory> namespace android { @@ -60,18 +57,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 +71,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 +78,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 +141,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 +171,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 +188,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); @@ -250,8 +202,7 @@ public: static void recordNode(RenderNode& node, std::function<void(Canvas&)> contentCallback) { std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas( - node.stagingProperties().getWidth(), node.stagingProperties().getHeight(), - &node)); + node.stagingProperties().getWidth(), node.stagingProperties().getHeight(), &node)); contentCallback(*canvas.get()); node.setStagingDisplayList(canvas->finishRecording()); } @@ -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); @@ -321,19 +267,21 @@ public: renderthread::RenderThread::getInstance().queue().runSync([&]() { task.run(); }); } + static void runOnRenderThreadUnmanaged(RtCallback rtCallback) { + auto& rt = renderthread::RenderThread::getInstance(); + rt.queue().runSync([&]() { rtCallback(rt); }); + } + + static bool isRenderThreadRunning() { return renderthread::RenderThread::hasInstance(); } + static pid_t getRenderThreadTid() { return renderthread::RenderThread::getInstance().getTid(); } 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, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x, float y); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, const SkPath& path); static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str); @@ -355,7 +303,52 @@ public: static SkRect getClipBounds(const SkCanvas* canvas); static SkRect getLocalClipBounds(const SkCanvas* canvas); + struct CallCounts { + int sync = 0; + int contextDestroyed = 0; + int destroyed = 0; + int glesDraw = 0; + }; + + static void expectOnRenderThread() { EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()); } + + static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) { + auto callbacks = WebViewFunctorCallbacks{ + .onSync = + [](int functor, void* client_data, const WebViewSyncData& data) { + expectOnRenderThread(); + sMockFunctorCounts[functor].sync++; + }, + .onContextDestroyed = + [](int functor, void* client_data) { + expectOnRenderThread(); + sMockFunctorCounts[functor].contextDestroyed++; + }, + .onDestroyed = + [](int functor, void* client_data) { + expectOnRenderThread(); + sMockFunctorCounts[functor].destroyed++; + }, + }; + switch (mode) { + case RenderMode::OpenGL_ES: + callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params) { + expectOnRenderThread(); + sMockFunctorCounts[functor].glesDraw++; + }; + break; + default: + ADD_FAILURE(); + return WebViewFunctorCallbacks{}; + } + return callbacks; + } + + static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; } + private: + static std::unordered_map<int, CallCounts> sMockFunctorCounts; + static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { MarkAndSweepRemoved observer(nullptr); node->syncProperties(); @@ -365,16 +358,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/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp index 4ecb54c9f0fd..510766073b08 100644 --- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp +++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp @@ -44,8 +44,7 @@ public: }); SkPaint paint; - sk_sp<SkColorFilter> colorFilter; - sk_sp<SkImage> image = hwuiBitmap->makeImage(&colorFilter); + sk_sp<SkImage> image = hwuiBitmap->makeImage(); sk_sp<SkShader> repeatShader = image->makeShader(SkShader::TileMode::kRepeat_TileMode, SkShader::TileMode::kRepeat_TileMode, nullptr); diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index f0a5e9dff1b9..0795d13f441b 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -51,7 +51,7 @@ public: paint.setAntiAlias(true); paint.setColor(Color::Black); for (int i = 0; i < 5; i++) { - paint.setTextSize(10 + (frameNr % 20) + i * 20); + paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20); TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2)); } diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index f137562e7c73..2af955fbb711 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -50,7 +50,8 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer)); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType, + SkColorSpace::MakeSRGB())); sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; @@ -72,8 +73,7 @@ public: void doFrame(int frameNr) override {} sk_sp<SkShader> createBitmapShader(Bitmap& bitmap) { - sk_sp<SkColorFilter> colorFilter; - sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); + sk_sp<SkImage> image = bitmap.makeImage(); return image->makeShader(SkShader::TileMode::kClamp_TileMode, SkShader::TileMode::kClamp_TileMode); } diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 58c99800875b..ecaaf487e4f8 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -16,7 +16,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" - +#include "hwui/Paint.h" #include <SkGradientShader.h> class ListOfFadedTextAnimation; @@ -33,8 +33,8 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase { canvas.drawColor(Color::White, SkBlendMode::kSrcOver); int length = dp(100); canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer); - SkPaint textPaint; - textPaint.setTextSize(dp(20)); + Paint textPaint; + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30)); diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index fd8c252ff318..feb881f654f8 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -16,7 +16,8 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" - +#include "hwui/Paint.h" +#include <SkFont.h> #include <cstdio> class ListViewAnimation; @@ -46,12 +47,13 @@ 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); + + SkFont font; + font.setSize(size / 2); char charToShow = 'A' + (rand() % 26); - const SkPoint pos[] = {{SkIntToScalar(size / 2), - /*approximate centering*/ SkFloatToScalar(size * 0.7f)}}; - canvas.drawPosText(&charToShow, 1, pos, paint); + const SkPoint pos = {SkIntToScalar(size / 2), + /*approximate centering*/ SkFloatToScalar(size * 0.7f)}; + canvas.drawSimpleText(&charToShow, 1, kUTF8_SkTextEncoding, pos.fX, pos.fY, font, paint); return bitmap; } @@ -82,14 +84,14 @@ class ListViewAnimation : public TestListViewSceneBase { roundRectPaint.setColor(Color::White); canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint); - SkPaint textPaint; + Paint textPaint; textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); - textPaint.setTextSize(dp(20)); + textPaint.getSkFont().setSize(dp(20)); textPaint.setAntiAlias(true); char buf[256]; snprintf(buf, sizeof(buf), "This card is #%d", cardId); TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25)); - textPaint.setTextSize(dp(15)); + textPaint.getSkFont().setSize(dp(15)); TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint, itemHeight, dp(45)); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index aa537b4f329c..f6cff1c643a1 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "renderthread/RenderProxy.h" #include "utils/Color.h" +#include "hwui/Paint.h" class MagnifierAnimation; @@ -37,9 +38,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode( 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400); diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 3befce4a395f..8630be87c09c 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -41,9 +41,9 @@ public: int top = bounds.fTop; mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255)); - mBluePaint.setTextSize(padding); + mBluePaint.getSkFont().setSize(padding); mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0)); - mGreenPaint.setTextSize(padding); + mGreenPaint.getSkFont().setSize(padding); // interleave drawText and drawRect with saveLayer ops for (int i = 0; i < regions; i++, top += smallRectHeight) { 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/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index a16b17849fc6..d30903679bce 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -15,6 +15,7 @@ */ #include "TestSceneBase.h" +#include "hwui/Paint.h" class TextAnimation; @@ -28,9 +29,9 @@ public: canvas.drawColor(Color::White, SkBlendMode::kSrcOver); card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props, Canvas& canvas) { - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(50); + paint.getSkFont().setSize(50); paint.setColor(Color::Black); for (int i = 0; i < 10; i++) { diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp index 286f5f194aed..229c7f392629 100644 --- a/libs/hwui/tests/common/scenes/TvApp.cpp +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -17,6 +17,7 @@ #include "SkBlendMode.h" #include "TestSceneBase.h" #include "tests/common/BitmapAllocationTestUtils.h" +#include "hwui/Paint.h" class TvApp; class TvAppNoRoundedCorner; @@ -116,13 +117,13 @@ private: [text, text2](RenderProperties& props, Canvas& canvas) { canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); - SkPaint paint; + Paint paint; paint.setAntiAlias(true); - paint.setTextSize(24); + paint.getSkFont().setSize(24); paint.setColor(Color::Black); TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30); - paint.setTextSize(20); + paint.getSkFont().setSize(20); TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54); }); diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp index adfae5bccebe..9c845f04e820 100644 --- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp +++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp @@ -21,6 +21,7 @@ #include "tests/common/TestContext.h" #include "tests/common/TestScene.h" #include "tests/common/scenes/TestSceneBase.h" +#include "utils/TraceUtils.h" #include <benchmark/benchmark.h> #include <gui/Surface.h> @@ -109,7 +110,7 @@ void outputBenchmarkReport(const TestScene::Info& info, const TestScene::Options void run(const TestScene::Info& info, const TestScene::Options& opts, benchmark::BenchmarkReporter* reporter) { // Switch to the real display - gDisplay = getBuiltInDisplay(); + gDisplay = getInternalDisplay(); Properties::forceDrawFrame = true; TestContext testContext; @@ -132,10 +133,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; @@ -153,6 +154,11 @@ void run(const TestScene::Info& info, const TestScene::Options& opts, proxy->resetProfileInfo(); proxy->fence(); + if (opts.renderAhead) { + usleep(33000); + } + proxy->setRenderAheadDepth(opts.renderAhead); + ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight); nsecs_t start = systemTime(CLOCK_MONOTONIC); diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 6caaf8d7616e..88d33c315a09 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -19,7 +19,8 @@ #include "Properties.h" #include "hwui/Typeface.h" -#include "protos/hwui.pb.h" +#include "HardwareBitmapUploader.h" +#include "renderthread/RenderProxy.h" #include <benchmark/benchmark.h> #include <getopt.h> @@ -67,7 +68,8 @@ 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 + --render-ahead=NUM Sets how far to render-ahead. Must be 0 (default), 1, or 2. )"); } @@ -145,9 +147,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); @@ -171,6 +171,7 @@ enum { Onscreen, Offscreen, Renderer, + RenderAhead, }; } @@ -186,6 +187,7 @@ static const struct option LONG_OPTIONS[] = { {"onscreen", no_argument, nullptr, LongOpts::Onscreen}, {"offscreen", no_argument, nullptr, LongOpts::Offscreen}, {"renderer", required_argument, nullptr, LongOpts::Renderer}, + {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead}, {0, 0, 0, 0}}; static const char* SHORT_OPTIONS = "c:r:h"; @@ -284,6 +286,16 @@ void parseOptions(int argc, char* argv[]) { gOpts.renderOffscreen = true; break; + case LongOpts::RenderAhead: + if (!optarg) { + error = true; + } + gOpts.renderAhead = atoi(optarg); + if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) { + error = true; + } + break; + case 'h': printHelp(); exit(EXIT_SUCCESS); @@ -356,6 +368,9 @@ int main(int argc, char* argv[]) { gBenchmarkReporter->Finalize(); } + renderthread::RenderProxy::trimMemory(100); + HardwareBitmapUploader::terminate(); + LeakChecker::checkForLeaks(); return 0; } 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/microbench/TaskManagerBench.cpp b/libs/hwui/tests/microbench/TaskManagerBench.cpp deleted file mode 100644 index 4153baec22b5..000000000000 --- a/libs/hwui/tests/microbench/TaskManagerBench.cpp +++ /dev/null @@ -1,134 +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 "thread/Task.h" -#include "thread/TaskManager.h" -#include "thread/TaskProcessor.h" -#include "thread/ThreadBase.h" - -#include <atomic> -#include <vector> - -using namespace android; -using namespace android::uirenderer; - -class TrivialTask : public Task<char> {}; - -class TrivialProcessor : public TaskProcessor<char> { -public: - explicit TrivialProcessor(TaskManager* manager) : TaskProcessor(manager) {} - virtual ~TrivialProcessor() {} - virtual void onProcess(const sp<Task<char>>& task) override { - TrivialTask* t = static_cast<TrivialTask*>(task.get()); - t->setResult(reinterpret_cast<intptr_t>(t) % 16 == 0 ? 'a' : 'b'); - } -}; - -class TestThread : public ThreadBase, public virtual RefBase {}; - -void BM_TaskManager_allocateTask(benchmark::State& state) { - std::vector<sp<TrivialTask>> tasks; - tasks.reserve(state.max_iterations); - - while (state.KeepRunning()) { - tasks.emplace_back(new TrivialTask); - benchmark::DoNotOptimize(tasks.back()); - } -} -BENCHMARK(BM_TaskManager_allocateTask); - -void BM_TaskManager_enqueueTask(benchmark::State& state) { - TaskManager taskManager; - sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager)); - std::vector<sp<TrivialTask>> tasks; - tasks.reserve(state.max_iterations); - - while (state.KeepRunning()) { - tasks.emplace_back(new TrivialTask); - benchmark::DoNotOptimize(tasks.back()); - processor->add(tasks.back()); - } - - for (sp<TrivialTask>& task : tasks) { - task->getResult(); - } -} -BENCHMARK(BM_TaskManager_enqueueTask); - -void BM_TaskManager_enqueueRunDeleteTask(benchmark::State& state) { - TaskManager taskManager; - sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager)); - std::vector<sp<TrivialTask>> tasks; - tasks.reserve(state.max_iterations); - - while (state.KeepRunning()) { - tasks.emplace_back(new TrivialTask); - benchmark::DoNotOptimize(tasks.back()); - processor->add(tasks.back()); - } - state.ResumeTiming(); - for (sp<TrivialTask>& task : tasks) { - benchmark::DoNotOptimize(task->getResult()); - } - tasks.clear(); - state.PauseTiming(); -} -BENCHMARK(BM_TaskManager_enqueueRunDeleteTask); - -void BM_Thread_enqueueTask(benchmark::State& state) { - sp<TestThread> thread{new TestThread}; - thread->start(); - - atomic_int counter(0); - int expected = 0; - while (state.KeepRunning()) { - expected++; - thread->queue().post([&counter]() { counter++; }); - } - thread->queue().runSync([]() {}); - - thread->requestExit(); - thread->join(); - if (counter != expected) { - printf("Ran %d lambads, should have been %d\n", counter.load(), expected); - } -} -BENCHMARK(BM_Thread_enqueueTask); - -void BM_Thread_enqueueRunDeleteTask(benchmark::State& state) { - sp<TestThread> thread{new TestThread}; - thread->start(); - std::vector<std::future<int>> tasks; - tasks.reserve(state.max_iterations); - - int expected = 0; - while (state.KeepRunning()) { - tasks.emplace_back(thread->queue().async([expected]() -> int { return expected + 1; })); - expected++; - } - state.ResumeTiming(); - expected = 0; - for (auto& future : tasks) { - if (future.get() != ++expected) { - printf("Mismatch expected %d vs. observed %d\n", expected, future.get()); - } - } - tasks.clear(); - state.PauseTiming(); -} -BENCHMARK(BM_Thread_enqueueRunDeleteTask);
\ No newline at end of file diff --git a/libs/hwui/tests/scripts/prep_generic.sh b/libs/hwui/tests/scripts/prep_generic.sh new file mode 100755 index 000000000000..223bf373c65a --- /dev/null +++ b/libs/hwui/tests/scripts/prep_generic.sh @@ -0,0 +1,249 @@ +# +# 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. +# + +################################################################# +### +### DO NOT MODIFY THIS FILE +### This is a copy of androidx's benchmark/lockClocks.sh +### Make changes there instead then copy here! +### +################################################################# + +# This script can be used to lock device clocks to stable levels for comparing +# different versions of software. Since the clock levels are not necessarily +# indicative of real world behavior, this should **never** be used to compare +# performance between different device models. + +# Fun notes for maintaining this file: +# `expr` can deal with ints > INT32_MAX, but if compares cannot. This is why we use MHz. +# `expr` can sometimes evaluate right-to-left. This is why we use parens. +# Everything below the initial host-check isn't bash - Android uses mksh +# mksh allows `\n` in an echo, bash doesn't +# can't use `awk` + +CPU_TARGET_FREQ_PERCENT=50 +GPU_TARGET_FREQ_PERCENT=50 + +if [ "`command -v getprop`" == "" ]; then + if [ -n "`command -v adb`" ]; then + echo "" + echo "Pushing $0 and running it on device..." + dest=/data/local/tmp/`basename $0` + adb push $0 ${dest} + adb shell ${dest} + adb shell rm ${dest} + exit + else + echo "Could not find adb. Options are:" + echo " 1. Ensure adb is on your \$PATH" + echo " 2. Use './gradlew lockClocks'" + echo " 3. Manually adb push this script to your device, and run it there" + exit -1 + fi +fi + +# require root +if [ "`id -u`" -ne "0" ]; then + echo "Not running as root, cannot lock clocks, aborting" + exit -1 +fi + +DEVICE=`getprop ro.product.device` +MODEL=`getprop ro.product.model` + +# Find CPU max frequency, and lock big cores to an available frequency +# that's >= $CPU_TARGET_FREQ_PERCENT% of max. Disable other cores. +function_lock_cpu() { + CPU_BASE=/sys/devices/system/cpu + GOV=cpufreq/scaling_governor + + # Find max CPU freq, and associated list of available freqs + cpuMaxFreq=0 + cpuAvailFreqCmpr=0 + cpuAvailFreq=0 + enableIndices='' + disableIndices='' + cpu=0 + while [ -f ${CPU_BASE}/cpu${cpu}/online ]; do + # enable core, so we can find its frequencies + echo 1 > ${CPU_BASE}/cpu${cpu}/online + + maxFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/cpuinfo_max_freq` + availFreq=`cat ${CPU_BASE}/cpu$cpu/cpufreq/scaling_available_frequencies` + availFreqCmpr=${availFreq// /-} + + if [ ${maxFreq} -gt ${cpuMaxFreq} ]; then + # new highest max freq, look for cpus with same max freq and same avail freq list + cpuMaxFreq=${maxFreq} + cpuAvailFreq=${availFreq} + cpuAvailFreqCmpr=${availFreqCmpr} + + if [ -z ${disableIndices} ]; then + disableIndices="$enableIndices" + else + disableIndices="$disableIndices $enableIndices" + fi + enableIndices=${cpu} + elif [ ${maxFreq} == ${cpuMaxFreq} ] && [ ${availFreqCmpr} == ${cpuAvailFreqCmpr} ]; then + enableIndices="$enableIndices $cpu" + else + disableIndices="$disableIndices $cpu" + fi + cpu=$(($cpu + 1)) + done + + # Chose a frequency to lock to that's >= $CPU_TARGET_FREQ_PERCENT% of max + # (below, 100M = 1K for KHz->MHz * 100 for %) + TARGET_FREQ_MHZ=`expr \( ${cpuMaxFreq} \* ${CPU_TARGET_FREQ_PERCENT} \) \/ 100000` + chosenFreq=0 + for freq in ${cpuAvailFreq}; do + freqMhz=`expr ${freq} \/ 1000` + if [ ${freqMhz} -ge ${TARGET_FREQ_MHZ} ]; then + chosenFreq=${freq} + break + fi + done + + # enable 'big' CPUs + for cpu in ${enableIndices}; do + freq=${CPU_BASE}/cpu$cpu/cpufreq + + echo 1 > ${CPU_BASE}/cpu${cpu}/online + echo userspace > ${CPU_BASE}/cpu${cpu}/${GOV} + echo ${chosenFreq} > ${freq}/scaling_max_freq + echo ${chosenFreq} > ${freq}/scaling_min_freq + echo ${chosenFreq} > ${freq}/scaling_setspeed + + # validate setting the freq worked + obsCur=`cat ${freq}/scaling_cur_freq` + obsMin=`cat ${freq}/scaling_min_freq` + obsMax=`cat ${freq}/scaling_max_freq` + if [ obsCur -ne ${chosenFreq} ] || [ obsMin -ne ${chosenFreq} ] || [ obsMax -ne ${chosenFreq} ]; then + echo "Failed to set CPU$cpu to $chosenFreq Hz! Aborting..." + echo "scaling_cur_freq = $obsCur" + echo "scaling_min_freq = $obsMin" + echo "scaling_max_freq = $obsMax" + exit -1 + fi + done + + # disable other CPUs (Note: important to enable big cores first!) + for cpu in ${disableIndices}; do + echo 0 > ${CPU_BASE}/cpu${cpu}/online + done + + echo "\nLocked CPUs ${enableIndices// /,} to $chosenFreq / $maxFreq KHz" + echo "Disabled CPUs ${disableIndices// /,}" +} + +# If we have a Qualcomm GPU, find its max frequency, and lock to +# an available frequency that's >= GPU_TARGET_FREQ_PERCENT% of max. +function_lock_gpu_kgsl() { + if [ ! -d /sys/class/kgsl/kgsl-3d0/ ]; then + # not kgsl, abort + echo "\nCurrently don't support locking GPU clocks of $MODEL ($DEVICE)" + return -1 + fi + if [ ${DEVICE} == "walleye" ] || [ ${DEVICE} == "taimen" ]; then + # Workaround crash + echo "\nUnable to lock GPU clocks of $MODEL ($DEVICE)" + return -1 + fi + + GPU_BASE=/sys/class/kgsl/kgsl-3d0 + + gpuMaxFreq=0 + gpuAvailFreq=`cat $GPU_BASE/devfreq/available_frequencies` + for freq in ${gpuAvailFreq}; do + if [ ${freq} -gt ${gpuMaxFreq} ]; then + gpuMaxFreq=${freq} + fi + done + + # (below, 100M = 1M for MHz * 100 for %) + TARGET_FREQ_MHZ=`expr \( ${gpuMaxFreq} \* ${GPU_TARGET_FREQ_PERCENT} \) \/ 100000000` + + chosenFreq=${gpuMaxFreq} + index=0 + chosenIndex=0 + for freq in ${gpuAvailFreq}; do + freqMhz=`expr ${freq} \/ 1000000` + if [ ${freqMhz} -ge ${TARGET_FREQ_MHZ} ] && [ ${chosenFreq} -ge ${freq} ]; then + # note avail freq are generally in reverse order, so we don't break out of this loop + chosenFreq=${freq} + chosenIndex=${index} + fi + index=$(($index + 1)) + done + lastIndex=$(($index - 1)) + + firstFreq=`echo $gpuAvailFreq | cut -d" " -f1` + + if [ ${gpuMaxFreq} != ${firstFreq} ]; then + # pwrlevel is index of desired freq among available frequencies, from highest to lowest. + # If gpuAvailFreq appears to be in-order, reverse the index + chosenIndex=$(($lastIndex - $chosenIndex)) + fi + + echo 0 > ${GPU_BASE}/bus_split + echo 1 > ${GPU_BASE}/force_clk_on + echo 10000 > ${GPU_BASE}/idle_timer + + echo performance > ${GPU_BASE}/devfreq/governor + + # NOTE: we store in min/max twice, because we don't know if we're increasing + # or decreasing, and it's invalid to try and set min > max, or max < min + echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq + echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq + echo ${chosenFreq} > ${GPU_BASE}/devfreq/min_freq + echo ${chosenFreq} > ${GPU_BASE}/devfreq/max_freq + echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel + echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel + echo ${chosenIndex} > ${GPU_BASE}/min_pwrlevel + echo ${chosenIndex} > ${GPU_BASE}/max_pwrlevel + + obsCur=`cat ${GPU_BASE}/devfreq/cur_freq` + obsMin=`cat ${GPU_BASE}/devfreq/min_freq` + obsMax=`cat ${GPU_BASE}/devfreq/max_freq` + if [ obsCur -ne ${chosenFreq} ] || [ obsMin -ne ${chosenFreq} ] || [ obsMax -ne ${chosenFreq} ]; then + echo "Failed to set GPU to $chosenFreq Hz! Aborting..." + echo "cur_freq = $obsCur" + echo "min_freq = $obsMin" + echo "max_freq = $obsMax" + echo "index = $chosenIndex" + exit -1 + fi + echo "\nLocked GPU to $chosenFreq / $gpuMaxFreq Hz" +} + +# kill processes that manage thermals / scaling +stop thermal-engine +stop perfd +stop vendor.thermal-engine +stop vendor.perfd + +function_lock_cpu + +function_lock_gpu_kgsl + +# Memory bus - hardcoded per-device for now +if [ ${DEVICE} == "marlin" ] || [ ${DEVICE} == "sailfish" ]; then + echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/max_freq +else + echo "\nUnable to lock memory bus of $MODEL ($DEVICE)." +fi + +echo "\n$DEVICE clocks have been locked - to reset, reboot the device\n"
\ No newline at end of file diff --git a/libs/hwui/tests/scripts/prep_ryu.sh b/libs/hwui/tests/scripts/prep_ryu.sh index 7c6c0df8d58e..7c6c0df8d58e 100644..100755 --- a/libs/hwui/tests/scripts/prep_ryu.sh +++ b/libs/hwui/tests/scripts/prep_ryu.sh 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/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp index c235715073f5..3f1ef93c878c 100644 --- a/libs/hwui/tests/unit/CacheManagerTests.cpp +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -33,7 +33,7 @@ static size_t getCacheUsage(GrContext* grContext) { } RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) { - DisplayInfo displayInfo = renderThread.mainDisplayInfo(); + DisplayInfo displayInfo = DeviceInfo::get()->displayInfo(); GrContext* grContext = renderThread.getGrContext(); ASSERT_TRUE(grContext != nullptr); @@ -54,8 +54,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) { // create an image and pin it so that we have something with a unique key in the cache sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(displayInfo.w, displayInfo.h)); - sk_sp<SkColorFilter> filter; - sk_sp<SkImage> image = bitmap->makeImage(&filter); + sk_sp<SkImage> image = bitmap->makeImage(); ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext)); // attempt to trim all memory while we still hold strong refs 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/CommonPoolTests.cpp b/libs/hwui/tests/unit/CommonPoolTests.cpp new file mode 100644 index 000000000000..da6a2604a4b6 --- /dev/null +++ b/libs/hwui/tests/unit/CommonPoolTests.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "thread/CommonPool.h" + +#include <array> +#include <condition_variable> +#include <set> +#include <thread> +#include "unistd.h" + +using namespace android; +using namespace android::uirenderer; + +TEST(CommonPool, post) { + std::atomic_bool ran(false); + CommonPool::post([&ran] { ran = true; }); + for (int i = 0; !ran && i < 1000; i++) { + usleep(1); + } + EXPECT_TRUE(ran) << "Failed to flip atomic after 1 second"; +} + +// test currently relies on timings, which +// makes it flaky. Disable for now +TEST(DISABLED_CommonPool, threadCount) { + std::set<pid_t> threads; + std::array<std::future<pid_t>, 64> futures; + for (int i = 0; i < futures.size(); i++) { + futures[i] = CommonPool::async([] { + usleep(10); + return gettid(); + }); + } + for (auto& f : futures) { + threads.insert(f.get()); + } + EXPECT_EQ(threads.size(), CommonPool::THREAD_COUNT); + EXPECT_EQ(0, threads.count(gettid())); +} + +TEST(CommonPool, singleThread) { + std::mutex mutex; + std::condition_variable fence; + bool isProcessing = false; + bool queuedSecond = false; + + auto f1 = CommonPool::async([&] { + { + std::unique_lock lock{mutex}; + isProcessing = true; + fence.notify_all(); + while (!queuedSecond) { + fence.wait(lock); + } + } + return gettid(); + }); + + { + std::unique_lock lock{mutex}; + while (!isProcessing) { + fence.wait(lock); + } + } + + auto f2 = CommonPool::async([] { + return gettid(); + }); + + { + std::unique_lock lock{mutex}; + queuedSecond = true; + fence.notify_all(); + } + + auto tid1 = f1.get(); + auto tid2 = f2.get(); + EXPECT_EQ(tid1, tid2); + EXPECT_NE(gettid(), tid1); +} + +// Test currently relies on timings +// which makes it flaky, disable for now +TEST(DISABLED_CommonPool, fullQueue) { + std::mutex lock; + std::condition_variable fence; + bool signaled = false; + static constexpr auto QUEUE_COUNT = CommonPool::THREAD_COUNT + CommonPool::QUEUE_SIZE + 10; + std::atomic_int queuedCount{0}; + std::array<std::future<void>, QUEUE_COUNT> futures; + + std::thread queueThread{[&] { + for (int i = 0; i < QUEUE_COUNT; i++) { + futures[i] = CommonPool::async([&] { + std::unique_lock _lock{lock}; + while (!signaled) { + fence.wait(_lock); + } + }); + queuedCount++; + } + }}; + + int previous; + do { + previous = queuedCount.load(); + usleep(10000); + } while (previous != queuedCount.load()); + + EXPECT_GT(queuedCount.load(), CommonPool::QUEUE_SIZE); + EXPECT_LT(queuedCount.load(), QUEUE_COUNT); + + { + std::unique_lock _lock{lock}; + signaled = true; + fence.notify_all(); + } + + queueThread.join(); + EXPECT_EQ(queuedCount.load(), QUEUE_COUNT); + + // Ensure all our tasks are finished before return as they have references to the stack + for (auto& f : futures) { + f.get(); + } +} + +class ObjectTracker { + static std::atomic_int sGlobalCount; + +public: + ObjectTracker() { + sGlobalCount++; + } + ObjectTracker(const ObjectTracker&) { + sGlobalCount++; + } + ObjectTracker(ObjectTracker&&) { + sGlobalCount++; + } + ~ObjectTracker() { + sGlobalCount--; + } + + static int count() { return sGlobalCount.load(); } +}; + +std::atomic_int ObjectTracker::sGlobalCount{0}; + +TEST(CommonPool, asyncLifecycleCheck) { + ASSERT_EQ(0, ObjectTracker::count()); + { + ObjectTracker obj; + ASSERT_EQ(1, ObjectTracker::count()); + EXPECT_LT(1, CommonPool::async([obj] { return ObjectTracker::count(); }).get()); + } + CommonPool::waitForIdle(); + ASSERT_EQ(0, ObjectTracker::count()); +} + +TEST(CommonPool, syncLifecycleCheck) { + ASSERT_EQ(0, ObjectTracker::count()); + { + ObjectTracker obj; + ASSERT_EQ(1, ObjectTracker::count()); + EXPECT_LT(1, CommonPool::runSync([obj] { return ObjectTracker::count(); })); + } + CommonPool::waitForIdle(); + ASSERT_EQ(0, ObjectTracker::count()); +} diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp index f29830f0e34b..f4c3e13b0ea6 100644 --- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp +++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp @@ -15,11 +15,12 @@ */ #include "DeferredLayerUpdater.h" -#include "GlLayer.h" #include "Properties.h" #include "tests/common/TestUtils.h" +#include <SkBitmap.h> +#include <SkImage.h> #include <gtest/gtest.h> using namespace android; @@ -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, 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..1723c2eb4948 100644 --- a/libs/hwui/tests/unit/FatalTestCanvas.h +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -30,26 +30,6 @@ public: void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) { ADD_FAILURE() << "onDrawDRRect not expected in this test"; } - void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint& paint) { - ADD_FAILURE() << "onDrawText not expected in this test"; - } - void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint& paint) { - ADD_FAILURE() << "onDrawPosText not expected in this test"; - } - void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, - 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"; - } void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) { ADD_FAILURE() << "onDrawTextBlob not expected in this test"; } @@ -132,4 +112,4 @@ public: int mDrawCounter = 0; // counts how may draw calls of any kind were made to this canvas }; -}
\ No newline at end of file +} 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..dac888cd79ca 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.destroyRenderingContext(); }); } } 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/LayerUpdateQueueTests.cpp b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp index 217d63f9c2e1..41714ebd84b1 100644 --- a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp +++ b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp @@ -81,5 +81,5 @@ TEST(LayerUpdateQueue, clear) { EXPECT_TRUE(queue.entries().empty()); } -}; -}; +} +} 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..c813cd945905 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); @@ -354,9 +355,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { class ProjectionTestCanvas : public SkCanvas { public: ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {} - void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - mDrawCounter++; - } + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { mDrawCounter++; } int getDrawCounter() { return mDrawCounter; } @@ -369,7 +368,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { [](RenderProperties& properties, SkiaRecordingCanvas& canvas) { properties.setProjectionReceiver(true); }, - "B"); // a receiver with an empty display list + "B"); // a receiver with an empty display list auto projectingRipple = TestUtils::createSkiaNode( 0, 0, 100, 100, @@ -388,14 +387,14 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) { canvas.drawRenderNode(projectingRipple.get()); }, "C"); - auto parent = TestUtils::createSkiaNode( - 0, 0, 100, 100, - [&receiverBackground, &child](RenderProperties& properties, - SkiaRecordingCanvas& canvas) { - canvas.drawRenderNode(receiverBackground.get()); - canvas.drawRenderNode(child.get()); - }, - "A"); + auto parent = + TestUtils::createSkiaNode(0, 0, 100, 100, + [&receiverBackground, &child](RenderProperties& properties, + SkiaRecordingCanvas& canvas) { + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + }, + "A"); ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( CanvasContext::create(renderThread, false, parent.get(), &contextFactory)); @@ -460,7 +459,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { ProjectionLayer(int* drawCounter) : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr) , mDrawCounter(drawCounter) {} - virtual sk_sp<SkImage> onNewImageSnapshot() override { + virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { EXPECT_EQ(3, (*mDrawCounter)++); EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X, 300 - SCROLL_Y), @@ -537,7 +536,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); @@ -1057,7 +1056,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { public: FrameTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) override { + const SkPaint* paint, SrcRectConstraint constraint) override { mDrawCounter++; EXPECT_EQ(kLow_SkFilterQuality, paint->getFilterQuality()); } @@ -1075,7 +1074,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { FrameTestCanvas canvas; RenderNodeDrawable drawable(layerNode.get(), &canvas, true); canvas.drawDrawable(&drawable); - EXPECT_EQ(1, canvas.mDrawCounter); //make sure the layer was composed + EXPECT_EQ(1, canvas.mDrawCounter); // make sure the layer was composed // clean up layer pointer, so we can safely destruct RenderNode layerNode->setLayerSurface(nullptr); @@ -1094,7 +1093,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 +1108,35 @@ 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 +1160,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. @@ -1154,14 +1171,14 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { public: VectorDrawableTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) override { + const SkPaint* paint, SrcRectConstraint constraint) override { const int index = mDrawCounter++; switch (index) { case 0: EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT)); break; case 1: - EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH/2, CANVAS_HEIGHT)); + EXPECT_EQ(dst, SkRect::MakeWH(CANVAS_WIDTH / 2, CANVAS_HEIGHT)); break; default: ADD_FAILURE(); @@ -1171,17 +1188,18 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { VectorDrawable::Group* group = new VectorDrawable::Group(); sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group)); - vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH/10, CANVAS_HEIGHT/10); + vectorDrawable->mutateStagingProperties()->setScaledSize(CANVAS_WIDTH / 10, CANVAS_HEIGHT / 10); - auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, - [&](RenderProperties& props, SkiaRecordingCanvas& canvas) { - vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH, - CANVAS_HEIGHT)); - canvas.drawVectorDrawable(vectorDrawable.get()); - vectorDrawable->mutateStagingProperties()->setBounds(SkRect::MakeWH(CANVAS_WIDTH/2, - CANVAS_HEIGHT)); - canvas.drawVectorDrawable(vectorDrawable.get()); - }); + auto node = + TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [&](RenderProperties& props, SkiaRecordingCanvas& canvas) { + vectorDrawable->mutateStagingProperties()->setBounds( + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT)); + canvas.drawVectorDrawable(vectorDrawable.get()); + vectorDrawable->mutateStagingProperties()->setBounds( + SkRect::MakeWH(CANVAS_WIDTH / 2, CANVAS_HEIGHT)); + canvas.drawVectorDrawable(vectorDrawable.get()); + }); VectorDrawableTestCanvas canvas; RenderNodeDrawable drawable(node.get(), &canvas, true); diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 079520836bb5..1cd9bd8ee9d9 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -295,7 +295,8 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) { canvasContext->destroy(); } -RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { +// TODO: Is this supposed to work in SkiaGL/SkiaVK? +RENDERTHREAD_TEST(DISABLED_RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { VectorDrawable::Group* group = new VectorDrawable::Group(); sp<VectorDrawableRoot> vectorDrawable(new VectorDrawableRoot(group)); @@ -306,6 +307,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { ContextFactory contextFactory; std::unique_ptr<CanvasContext> canvasContext( CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + canvasContext->setSurface(nullptr); TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get()); DamageAccumulator damageAccumulator; LayerUpdateQueue layerUpdateQueue; @@ -321,7 +323,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { // Check that the VD is in the dislay list, and the layer update queue contains the correct // damage rect. EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables()); - EXPECT_FALSE(info.layerUpdateQueue->entries().empty()); + ASSERT_FALSE(info.layerUpdateQueue->entries().empty()); EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode.get()); EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage); canvasContext->destroy(); 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/DeviceInfoTests.cpp b/libs/hwui/tests/unit/RenderThreadTests.cpp index af37938915e5..af8ae7841af2 100644 --- a/libs/hwui/tests/unit/DeviceInfoTests.cpp +++ b/libs/hwui/tests/unit/RenderThreadTests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open 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,16 @@ * limitations under the License. */ -#include <DeviceInfo.h> - #include <gtest/gtest.h> + #include "tests/common/TestUtils.h" +#include <utils/Looper.h> using namespace android; using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; -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"; +RENDERTHREAD_TEST(RenderThread, isLooper) { + ASSERT_TRUE(Looper::getForThread() != nullptr); } + diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 43080a9460b3..87981f115763 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ -#include <gtest/gtest.h> -#include <dirent.h> #include <cutils/properties.h> -#include <cstdint> +#include <dirent.h> #include <errno.h> +#include <gtest/gtest.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <utils/Log.h> -#include "pipeline/skia/ShaderCache.h" +#include <cstdint> #include "FileBlobCache.h" +#include "pipeline/skia/ShaderCache.h" using namespace android::uirenderer::skiapipeline; @@ -48,18 +48,24 @@ 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 */ } /* namespace uirenderer */ } /* namespace android */ - namespace { std::string getExternalStorageFolder() { @@ -75,52 +81,63 @@ 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()) TEST(ShaderCacheTest, testWriteAndRead) { if (!folderExist(getExternalStorageFolder())) { - //don't run the test if external storage folder is not available + // don't run the test if external storage folder is not available return; } - std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; - std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; - //remove any test files from previous test run + // 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 + // read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save ShaderCache::get().initShaderDiskCache(); - //read a key - should not be found since the cache is empty + // read a key - should not be found since the cache is empty sk_sp<SkData> outVS; ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>()); - //write to the in-memory cache without storing on disk and verify we read the same values + // write to the in-memory cache without storing on disk and verify we read the same values sk_sp<SkData> inVS; setShader(inVS, "sassas"); ShaderCache::get().store(GrProgramDescTest(100), *inVS.get()); @@ -131,23 +148,23 @@ TEST(ShaderCacheTest, testWriteAndRead) { ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS, "someVS")); - //store content to disk and release in-memory cache + // store content to disk and release in-memory cache ShaderCacheTestUtils::terminate(ShaderCache::get(), true); - //change to a file that does not exist and verify load fails + // change to a file that does not exist and verify load fails ShaderCache::get().setFilename(cacheFile2.c_str()); ShaderCache::get().initShaderDiskCache(); ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>()); ShaderCacheTestUtils::terminate(ShaderCache::get(), false); - //load again content from disk from an existing file and check the data is read correctly + // load again content from disk from an existing file and check the data is read correctly ShaderCache::get().setFilename(cacheFile1.c_str()); ShaderCache::get().initShaderDiskCache(); sk_sp<SkData> outVS2; ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS2, "someVS")); - //change data, store to disk, read back again and verify data has been changed + // change data, store to disk, read back again and verify data has been changed setShader(inVS, "ewData1"); ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); @@ -155,13 +172,10 @@ TEST(ShaderCacheTest, testWriteAndRead) { ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS2, "ewData1")); - - //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; - } + // write and read big data chunk (50K) + size_t dataSize = 50 * 1024; + 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 +187,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..f6178aff0c2e 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()); @@ -80,8 +44,8 @@ TEST(SkiaCanvas, drawShadowLayer) { } TEST(SkiaCanvas, colorSpaceXform) { - sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kAdobeRGB_Gamut); + sk_sp<SkColorSpace> adobe = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, + SkNamedGamut::kAdobeRGB); SkImageInfo adobeInfo = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType, adobe); sk_sp<Bitmap> adobeBitmap = Bitmap::allocateHeapBitmap(adobeInfo); @@ -89,12 +53,12 @@ TEST(SkiaCanvas, colorSpaceXform) { adobeBitmap->getSkBitmap(&adobeSkBitmap); *adobeSkBitmap.getAddr32(0, 0) = 0xFF0000F0; // Opaque, almost fully-red - SkImageInfo info = adobeInfo.makeColorSpace(nullptr); + SkImageInfo info = adobeInfo.makeColorSpace(SkColorSpace::MakeSRGB()); sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info); SkBitmap skBitmap; bitmap->getSkBitmap(&skBitmap); - // Create a software canvas. + // Create a software sRGB canvas. SkiaCanvas canvas(skBitmap); canvas.drawBitmap(*adobeBitmap, 0, 0, nullptr); // The result should be fully red, since we convert to sRGB at draw time. @@ -113,7 +77,7 @@ TEST(SkiaCanvas, colorSpaceXform) { picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr); sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); - // Playback to an software canvas. The result should be fully red. + // Playback to a software sRGB canvas. The result should be fully red. canvas.asSkCanvas()->drawPicture(picture); ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0)); } diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 88d6dcf990a5..6fb164a99ae4 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -20,8 +20,10 @@ #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/TestContext.h" #include "tests/common/TestUtils.h" using namespace android; @@ -36,32 +38,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->mChildNodes.emplace_back(nullptr, &dummyCanvas); + GLFunctorDrawable functorDrawable(nullptr, nullptr, &dummyCanvas); + skiaDL->mChildFunctors.push_back(&functorDrawable); + skiaDL->mMutableImages.push_back(nullptr); + skiaDL->appendVD(nullptr); + skiaDL->mProjectionReceiver = &drawable; - skiaDL.reset(); + ASSERT_FALSE(skiaDL->mChildNodes.empty()); + ASSERT_FALSE(skiaDL->mChildFunctors.empty()); + ASSERT_FALSE(skiaDL->mMutableImages.empty()); + ASSERT_TRUE(skiaDL->hasVectorDrawables()); + ASSERT_FALSE(skiaDL->isEmpty()); + ASSERT_TRUE(skiaDL->mProjectionReceiver); - 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->reset(); + + ASSERT_TRUE(skiaDL->mChildNodes.empty()); + ASSERT_TRUE(skiaDL->mChildFunctors.empty()); + ASSERT_TRUE(skiaDL->mMutableImages.empty()); + ASSERT_FALSE(skiaDL->hasVectorDrawables()); + ASSERT_TRUE(skiaDL->isEmpty()); + ASSERT_FALSE(skiaDL->mProjectionReceiver); } TEST(SkiaDisplayList, reuseDisplayList) { @@ -91,18 +98,38 @@ 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); + + int functor2 = WebViewFunctor_create( + nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + auto& counts = TestUtils::countsForFunctor(functor2); + skiaDL.mChildFunctors.push_back( + skiaDL.allocateDrawable<GLFunctorDrawable>(functor2, &dummyCanvas)); + WebViewFunctor_release(functor2); SkRect bounds = SkRect::MakeWH(200, 200); VectorDrawableRoot vectorDrawable(new VectorDrawable::Group()); vectorDrawable.mutateStagingProperties()->setBounds(bounds); - skiaDL.mVectorDrawables.push_back(&vectorDrawable); + skiaDL.appendVD(&vectorDrawable); // ensure that the functor and vectorDrawable are properly synced - skiaDL.syncContents(); + TestUtils::runOnRenderThread([&](auto&) { + skiaDL.syncContents(WebViewSyncData{ + .applyForceDark = false, + }); + }); - ASSERT_EQ(functor.getLastMode(), DrawGlInfo::kModeSync); - ASSERT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); + EXPECT_EQ(functor.getLastMode(), DrawGlInfo::kModeSync); + EXPECT_EQ(counts.sync, 1); + EXPECT_EQ(counts.destroyed, 0); + EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); + + skiaDL.reset(); + TestUtils::runOnRenderThread([](auto&) { + // Fence + }); + EXPECT_EQ(counts.destroyed, 1); } class ContextFactory : public IContextFactory { @@ -123,9 +150,14 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { SkiaDisplayList skiaDL; + // The VectorDrawableRoot needs to have bounds on screen (and therefore not + // empty) in order to have PropertyChangeWillBeConsumed set. + const auto bounds = SkRect::MakeIWH(100, 100); + // prepare with a clean VD VectorDrawableRoot cleanVD(new VectorDrawable::Group()); - skiaDL.mVectorDrawables.push_back(&cleanVD); + cleanVD.mutateProperties()->setBounds(bounds); + skiaDL.appendVD(&cleanVD); cleanVD.getBitmapUpdateIfDirty(); // this clears the dirty bit ASSERT_FALSE(cleanVD.isDirty()); @@ -133,11 +165,12 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { TestUtils::MockTreeObserver observer; ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); - ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed()); + ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed()); // prepare again this time adding a dirty VD VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); - skiaDL.mVectorDrawables.push_back(&dirtyVD); + dirtyVD.mutateProperties()->setBounds(bounds); + skiaDL.appendVD(&dirtyVD); ASSERT_TRUE(dirtyVD.isDirty()); ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); @@ -165,6 +198,169 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) { canvasContext->destroy(); } +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) { + auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); + ContextFactory contextFactory; + std::unique_ptr<CanvasContext> canvasContext( + CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory)); + + // Set up a Surface so that we can position the VectorDrawable offscreen. + test::TestContext testContext; + testContext.setRenderOffscreen(true); + auto surface = testContext.surface(); + int width, height; + surface->query(NATIVE_WINDOW_WIDTH, &width); + surface->query(NATIVE_WINDOW_HEIGHT, &height); + canvasContext->setSurface(std::move(surface)); + + TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get()); + DamageAccumulator damageAccumulator; + info.damageAccumulator = &damageAccumulator; + + // The VectorDrawableRoot needs to have bounds on screen (and therefore not + // empty) in order to have PropertyChangeWillBeConsumed set. + const auto bounds = SkRect::MakeIWH(100, 100); + + for (const SkRect b : {bounds.makeOffset(width, 0), + bounds.makeOffset(0, height), + bounds.makeOffset(-bounds.width(), 0), + bounds.makeOffset(0, -bounds.height())}) { + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(b); + skiaDL.appendVD(&dirtyVD); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_FALSE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + } + + // The DamageAccumulator's transform can also result in the + // VectorDrawableRoot being offscreen. + for (const SkISize translate : { SkISize{width, 0}, + SkISize{0, height}, + SkISize{-width, 0}, + SkISize{0, -height}}) { + Matrix4 mat4; + mat4.translate(translate.fWidth, translate.fHeight); + damageAccumulator.pushTransform(&mat4); + + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(bounds); + skiaDL.appendVD(&dirtyVD); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_FALSE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + damageAccumulator.popTransform(); + } + + // Another way to be offscreen: a matrix from the draw call. + for (const SkMatrix translate : { SkMatrix::MakeTrans(width, 0), + SkMatrix::MakeTrans(0, height), + SkMatrix::MakeTrans(-width, 0), + SkMatrix::MakeTrans(0, -height)}) { + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(bounds); + skiaDL.appendVD(&dirtyVD, translate); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_FALSE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + } + + // Verify that the matrices are combined in the right order. + { + // Rotate and then translate, so the VD is offscreen. + Matrix4 mat4; + mat4.loadRotate(180); + damageAccumulator.pushTransform(&mat4); + + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(bounds); + SkMatrix translate = SkMatrix::MakeTrans(50, 50); + skiaDL.appendVD(&dirtyVD, translate); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_FALSE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + damageAccumulator.popTransform(); + } + { + // Switch the order of rotate and translate, so it is on screen. + Matrix4 mat4; + mat4.translate(50, 50); + damageAccumulator.pushTransform(&mat4); + + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(bounds); + SkMatrix rotate; + rotate.setRotate(180); + skiaDL.appendVD(&dirtyVD, rotate); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_TRUE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed()); + damageAccumulator.popTransform(); + } + { + // An AVD that is larger than the screen. + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(SkRect::MakeLTRB(-1, -1, width + 1, height + 1)); + skiaDL.appendVD(&dirtyVD); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_TRUE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed()); + } + { + // An AVD whose bounds are not a rectangle after applying a matrix. + SkiaDisplayList skiaDL; + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + dirtyVD.mutateProperties()->setBounds(bounds); + SkMatrix mat; + mat.setRotate(45, 50, 50); + skiaDL.appendVD(&dirtyVD, mat); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + + TestUtils::MockTreeObserver observer; + ASSERT_TRUE(skiaDL.prepareListAndChildren( + observer, info, false, [](RenderNode*, TreeObserver&, TreeInfo&, bool) {})); + ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed()); + } +} + TEST(SkiaDisplayList, updateChildren) { SkiaDisplayList skiaDL; diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 42a92fcc7d89..a671bdada09a 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.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" @@ -28,9 +27,13 @@ #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaRecordingCanvas.h" +#include "pipeline/skia/SkiaUtils.h" #include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" +#include <gui/BufferItemConsumer.h> +#include <gui/Surface.h> + using namespace android; using namespace android::uirenderer; using namespace android::uirenderer::renderthread; @@ -42,7 +45,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,8 +54,8 @@ 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -63,7 +66,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,8 +87,8 @@ 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -98,7 +101,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,12 +109,12 @@ 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface, + SkMatrix::I()); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); } @@ -130,8 +133,8 @@ 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface, + SkMatrix::I()); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED); @@ -161,18 +164,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,38 +206,38 @@ 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); 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, - surface); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080); } @@ -247,7 +250,7 @@ public: SkCanvas* onNewCanvas() override { return new T(); } sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; } - sk_sp<SkImage> onNewImageSnapshot() override { return nullptr; } + sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { return nullptr; } T* canvas() { return static_cast<T*>(getCanvas()); } void onCopyOnWrite(ContentChangeMode) override {} void onWritePixels(const SkPixmap&, int x, int y) override {} @@ -319,7 +322,8 @@ 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, + SkMatrix::I()); EXPECT_EQ(4, surface->canvas()->mDrawCounter); } @@ -349,8 +353,44 @@ 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, - SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I()); + EXPECT_EQ(1, surface->canvas()->mDrawCounter); +} + +// Test renderFrame with a dirty clip and a pre-transform matrix. +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) { + static const int CANVAS_WIDTH = 200; + static const int CANVAS_HEIGHT = 100; + static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1); + static const SkRect dirty = SkRect::MakeLTRB(10, 20, 20, 40); + class ClippedTestCanvas : public SkCanvas { + public: + ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {} + void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) override { + EXPECT_EQ(0, mDrawCounter++); + // Expect clip to be rotated. + EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft, + CANVAS_HEIGHT - dirty.fTop, dirty.fLeft + dirty.width()), + TestUtils::getClipBounds(this)); + EXPECT_EQ(rotateMatrix, getTotalMatrix()); + } + int mDrawCounter = 0; + }; + + std::vector<sp<RenderNode>> nodes; + nodes.push_back(TestUtils::createSkiaNode( + 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + sk_sp<Bitmap> bitmap(TestUtils::createBitmap(CANVAS_WIDTH, CANVAS_HEIGHT)); + canvas.drawBitmap(*bitmap, 0, 0, nullptr); + })); + + LayerUpdateQueue layerUpdateQueue; + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>()); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, rotateMatrix); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } @@ -379,7 +419,26 @@ 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, - SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, + SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface, SkMatrix::I()); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } + +static sp<Surface> createDummySurface() { + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + producer->setMaxDequeuedBufferCount(1); + producer->setAsyncMode(true); + return new Surface(producer); +} + +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { + auto surface = createDummySurface(); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + EXPECT_FALSE(pipeline->isSurfaceReady()); + EXPECT_TRUE(pipeline->setSurface(surface.get(), SwapBehavior::kSwap_default, ColorMode::SRGB, 0)); + EXPECT_TRUE(pipeline->isSurfaceReady()); + renderThread.destroyRenderingContext(); + EXPECT_FALSE(pipeline->isSurfaceReady()); +} diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp index 7deb0b1486ea..635429dea359 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..1a09b1c52d8a 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -54,8 +54,9 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); 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::shared_ptr<minikin::MinikinFont> font = + std::make_shared<MinikinFontSkia>(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/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index 4f299e377f09..5db002862fcd 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -85,8 +85,10 @@ const static TestData sTestDataSet[] = { outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0); outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0); - outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 10.0, 10.0); - outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 20.0, 20.0); + outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 10.0, + 10.0); + outPath->arcTo(10.0, 10.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCW_Direction, 20.0, + 20.0); }}, // Check box VectorDrawable path data @@ -157,7 +159,8 @@ const static TestData sTestDataSet[] = { }, [](SkPath* outPath) { outPath->moveTo(300.0, 70.0); - outPath->arcTo(230.0, 230.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCCW_Direction, 301.0, 70.0); + outPath->arcTo(230.0, 230.0, 0.0, SkPath::kLarge_ArcSize, SkPath::kCCW_Direction, + 301.0, 70.0); outPath->close(); outPath->moveTo(300.0, 70.0); }}, @@ -236,14 +239,14 @@ struct StringPath { }; const StringPath sStringPaths[] = { - {"3e...3", false}, // Not starting with a verb and ill-formatted float - {"L.M.F.A.O", false}, // No floats following verbs - {"m 1 1", true}, // Valid path data - {"\n \t z", true}, // Valid path data with leading spaces - {"1-2e34567", false}, // Not starting with a verb and ill-formatted float - {"f 4 5", false}, // Invalid verb - {"\r ", false}, // Empty string - {"L1,0 L1,1 L0,1 z M1000", false} // Not enough floats following verb M. + {"3e...3", false}, // Not starting with a verb and ill-formatted float + {"L.M.F.A.O", false}, // No floats following verbs + {"m 1 1", true}, // Valid path data + {"\n \t z", true}, // Valid path data with leading spaces + {"1-2e34567", false}, // Not starting with a verb and ill-formatted float + {"f 4 5", false}, // Invalid verb + {"\r ", false}, // Empty string + {"L1,0 L1,1 L0,1 z M1000", false} // Not enough floats following verb M. }; static bool hasSameVerbs(const PathData& from, const PathData& to) { @@ -406,5 +409,5 @@ TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) { EXPECT_TRUE(shader->unique()); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp new file mode 100644 index 000000000000..e1fb8b7069ff --- /dev/null +++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp @@ -0,0 +1,154 @@ +/* + * 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 <gtest/gtest.h> + +#include "WebViewFunctorManager.h" +#include "private/hwui/WebViewFunctor.h" +#include "renderthread/RenderProxy.h" +#include "tests/common/TestUtils.h" + +#include <unordered_map> + +using namespace android; +using namespace android::uirenderer; + +TEST(WebViewFunctor, createDestroyGLES) { + int functor = WebViewFunctor_create( + nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + WebViewFunctor_release(functor); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // Empty, don't care + }); + auto& counts = TestUtils::countsForFunctor(functor); + // We never initialized, so contextDestroyed == 0 + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, createSyncHandleGLES) { + int functor = WebViewFunctor_create( + nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + EXPECT_FALSE(WebViewFunctorManager::instance().handleFor(functor)); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + auto& counts = TestUtils::countsForFunctor(functor); + EXPECT_EQ(0, counts.sync); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + }); + + EXPECT_EQ(1, counts.sync); + + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + }); + + EXPECT_EQ(2, counts.sync); + + handle.clear(); + + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, createSyncDrawGLES) { + int functor = WebViewFunctor_create( + nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + auto& counts = TestUtils::countsForFunctor(functor); + for (int i = 0; i < 5; i++) { + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + handle->drawGl(drawInfo); + }); + } + handle.clear(); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + EXPECT_EQ(5, counts.sync); + EXPECT_EQ(10, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} + +TEST(WebViewFunctor, contextDestroyed) { + int functor = WebViewFunctor_create( + nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES); + ASSERT_NE(-1, functor); + auto handle = WebViewFunctorManager::instance().handleFor(functor); + ASSERT_TRUE(handle); + WebViewFunctor_release(functor); + auto& counts = TestUtils::countsForFunctor(functor); + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + }); + EXPECT_EQ(1, counts.sync); + EXPECT_EQ(1, counts.glesDraw); + EXPECT_EQ(0, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + TestUtils::runOnRenderThreadUnmanaged([](auto& rt) { + rt.destroyRenderingContext(); + }); + EXPECT_EQ(1, counts.sync); + EXPECT_EQ(1, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + TestUtils::runOnRenderThreadUnmanaged([&](auto&) { + WebViewSyncData syncData; + handle->sync(syncData); + DrawGlInfo drawInfo; + handle->drawGl(drawInfo); + }); + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(2, counts.glesDraw); + EXPECT_EQ(1, counts.contextDestroyed); + EXPECT_EQ(0, counts.destroyed); + handle.clear(); + TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { + // fence + }); + EXPECT_EQ(2, counts.sync); + EXPECT_EQ(2, counts.glesDraw); + EXPECT_EQ(2, counts.contextDestroyed); + EXPECT_EQ(1, counts.destroyed); +} diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp index 9e6d9a8c27de..83d888c310f0 100644 --- a/libs/hwui/tests/unit/main.cpp +++ b/libs/hwui/tests/unit/main.cpp @@ -17,13 +17,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "Caches.h" +#include "Properties.h" #include "debug/GlesDriver.h" #include "debug/NullGlesDriver.h" #include "hwui/Typeface.h" -#include "Properties.h" #include "tests/common/LeakChecker.h" -#include "thread/TaskManager.h" #include <signal.h> diff --git a/libs/hwui/thread/Barrier.h b/libs/hwui/thread/Barrier.h deleted file mode 100644 index 8faeee6b391a..000000000000 --- a/libs/hwui/thread/Barrier.h +++ /dev/null @@ -1,54 +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_BARRIER_H -#define ANDROID_HWUI_BARRIER_H - -#include <utils/Condition.h> - -namespace android { -namespace uirenderer { - -class Barrier { -public: - explicit Barrier(Condition::WakeUpType type = Condition::WAKE_UP_ALL) - : mType(type), mOpened(false) {} - ~Barrier() {} - - void open() { - Mutex::Autolock l(mLock); - mOpened = true; - mCondition.signal(mType); - } - - void wait() const { - Mutex::Autolock l(mLock); - while (!mOpened) { - mCondition.wait(mLock); - } - } - -private: - Condition::WakeUpType mType; - volatile bool mOpened; - mutable Mutex mLock; - mutable Condition mCondition; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_BARRIER_H diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp new file mode 100644 index 000000000000..d011bdfe945e --- /dev/null +++ b/libs/hwui/thread/CommonPool.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "CommonPool.h" + +#include <sys/resource.h> +#include <utils/Trace.h> +#include "renderthread/RenderThread.h" + +#include <array> + +namespace android { +namespace uirenderer { + +CommonPool::CommonPool() { + ATRACE_CALL(); + + CommonPool* pool = this; + // Create 2 workers + for (int i = 0; i < THREAD_COUNT; i++) { + std::thread worker([pool, i] { + { + std::array<char, 20> name{"hwuiTask"}; + snprintf(name.data(), name.size(), "hwuiTask%d", i); + auto self = pthread_self(); + pthread_setname_np(self, name.data()); + setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND); + auto startHook = renderthread::RenderThread::getOnStartHook(); + if (startHook) { + startHook(name.data()); + } + } + pool->workerLoop(); + }); + worker.detach(); + } +} + +CommonPool& CommonPool::instance() { + static CommonPool pool; + return pool; +} + +void CommonPool::post(Task&& task) { + instance().enqueue(std::move(task)); +} + +void CommonPool::enqueue(Task&& task) { + std::unique_lock lock(mLock); + while (!mWorkQueue.hasSpace()) { + lock.unlock(); + usleep(100); + lock.lock(); + } + mWorkQueue.push(std::move(task)); + if (mWaitingThreads == THREAD_COUNT || (mWaitingThreads > 0 && mWorkQueue.size() > 1)) { + mCondition.notify_one(); + } +} + +void CommonPool::workerLoop() { + std::unique_lock lock(mLock); + while (true) { + if (!mWorkQueue.hasWork()) { + mWaitingThreads++; + mCondition.wait(lock); + mWaitingThreads--; + } + // Need to double-check that work is still available now that we have the lock + // It may have already been grabbed by a different thread + while (mWorkQueue.hasWork()) { + auto work = mWorkQueue.pop(); + lock.unlock(); + work(); + lock.lock(); + } + } +} + +void CommonPool::waitForIdle() { + instance().doWaitForIdle(); +} + +void CommonPool::doWaitForIdle() { + std::unique_lock lock(mLock); + while (mWaitingThreads != THREAD_COUNT) { + lock.unlock(); + usleep(100); + lock.lock(); + } +} + +} // namespace uirenderer +} // namespace android
\ No newline at end of file diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h new file mode 100644 index 000000000000..7603eeef4692 --- /dev/null +++ b/libs/hwui/thread/CommonPool.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 FRAMEWORKS_BASE_COMMONPOOL_H +#define FRAMEWORKS_BASE_COMMONPOOL_H + +#include "utils/Macros.h" + +#include <log/log.h> + +#include <condition_variable> +#include <functional> +#include <future> +#include <mutex> + +namespace android { +namespace uirenderer { + +template <class T, int SIZE> +class ArrayQueue { + PREVENT_COPY_AND_ASSIGN(ArrayQueue); + static_assert(SIZE > 0, "Size must be positive"); + +public: + ArrayQueue() = default; + ~ArrayQueue() = default; + + constexpr size_t capacity() const { return SIZE; } + constexpr bool hasWork() const { return mHead != mTail; } + constexpr bool hasSpace() const { return ((mHead + 1) % SIZE) != mTail; } + constexpr int size() const { + if (mHead > mTail) { + return mHead - mTail; + } else { + return mTail - mHead + SIZE; + } + } + + constexpr void push(T&& t) { + int newHead = (mHead + 1) % SIZE; + LOG_ALWAYS_FATAL_IF(newHead == mTail, "no space"); + + mBuffer[mHead] = std::move(t); + mHead = newHead; + } + + constexpr T pop() { + LOG_ALWAYS_FATAL_IF(mTail == mHead, "empty"); + int index = mTail; + mTail = (mTail + 1) % SIZE; + T ret = std::move(mBuffer[index]); + mBuffer[index] = nullptr; + return ret; + } + +private: + T mBuffer[SIZE]; + int mHead = 0; + int mTail = 0; +}; + +class CommonPool { + PREVENT_COPY_AND_ASSIGN(CommonPool); + +public: + using Task = std::function<void()>; + static constexpr auto THREAD_COUNT = 2; + static constexpr auto QUEUE_SIZE = 128; + + static void post(Task&& func); + + template <class F> + static auto async(F&& func) -> std::future<decltype(func())> { + typedef std::packaged_task<decltype(func())()> task_t; + auto task = std::make_shared<task_t>(std::forward<F>(func)); + post([task]() { std::invoke(*task); }); + return task->get_future(); + } + + template <class F> + static auto runSync(F&& func) -> decltype(func()) { + std::packaged_task<decltype(func())()> task{std::forward<F>(func)}; + post([&task]() { std::invoke(task); }); + return task.get_future().get(); + }; + + // For testing purposes only, blocks until all worker threads are parked. + static void waitForIdle(); + +private: + static CommonPool& instance(); + + CommonPool(); + ~CommonPool() {} + + void enqueue(Task&&); + void doWaitForIdle(); + + void workerLoop(); + + std::mutex mLock; + std::condition_variable mCondition; + int mWaitingThreads = 0; + ArrayQueue<Task, QUEUE_SIZE> mWorkQueue; +}; + +} // namespace uirenderer +} // namespace android + +#endif // FRAMEWORKS_BASE_COMMONPOOL_H diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h deleted file mode 100644 index 45f3102492e3..000000000000 --- a/libs/hwui/thread/Future.h +++ /dev/null @@ -1,59 +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_FUTURE_H -#define ANDROID_HWUI_FUTURE_H - -#include <utils/RefBase.h> - -#include "Barrier.h" - -namespace android { -namespace uirenderer { - -template <typename T> -class Future : public LightRefBase<Future<T> > { -public: - explicit Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE) - : mBarrier(type), mResult() {} - ~Future() {} - - /** - * Returns the result of this future, blocking if - * the result is not available yet. - */ - T get() const { - mBarrier.wait(); - return mResult; - } - - /** - * This method must be called only once. - */ - void produce(T result) { - mResult = result; - mBarrier.open(); - } - -private: - Barrier mBarrier; - T mResult; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_FUTURE_H diff --git a/libs/hwui/thread/Signal.h b/libs/hwui/thread/Signal.h deleted file mode 100644 index ffcd4b675a85..000000000000 --- a/libs/hwui/thread/Signal.h +++ /dev/null @@ -1,59 +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_SIGNAL_H -#define ANDROID_HWUI_SIGNAL_H - -#include <stdint.h> -#include <sys/types.h> -#include <utils/threads.h> - -namespace android { -namespace uirenderer { - -class Signal { -public: - explicit Signal(Condition::WakeUpType type = Condition::WAKE_UP_ALL) - : mType(type), mSignaled(false) {} - ~Signal() {} - - void signal() { - { - Mutex::Autolock l(mLock); - mSignaled = true; - } - mCondition.signal(mType); - } - - void wait() { - Mutex::Autolock l(mLock); - while (!mSignaled) { - mCondition.wait(mLock); - } - mSignaled = false; - } - -private: - Condition::WakeUpType mType; - volatile bool mSignaled; - mutable Mutex mLock; - mutable Condition mCondition; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SIGNAL_H diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h deleted file mode 100644 index 276a22f941fe..000000000000 --- a/libs/hwui/thread/Task.h +++ /dev/null @@ -1,54 +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_TASK_H -#define ANDROID_HWUI_TASK_H - -#include <utils/RefBase.h> -#include <utils/Trace.h> - -#include "Future.h" - -namespace android { -namespace uirenderer { - -class TaskBase : public RefBase { -public: - TaskBase() {} - virtual ~TaskBase() {} -}; - -template <typename T> -class Task : public TaskBase { -public: - Task() : mFuture(new Future<T>()) {} - virtual ~Task() {} - - T getResult() const { return mFuture->get(); } - - void setResult(T result) { mFuture->produce(result); } - -protected: - const sp<Future<T> >& future() const { return mFuture; } - -private: - sp<Future<T> > mFuture; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TASK_H diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp deleted file mode 100644 index 54b55e472095..000000000000 --- a/libs/hwui/thread/TaskManager.cpp +++ /dev/null @@ -1,133 +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 <sys/resource.h> -#include <sys/sysinfo.h> - -#include "Task.h" -#include "TaskManager.h" -#include "TaskProcessor.h" -#include "utils/MathUtils.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Manager -/////////////////////////////////////////////////////////////////////////////// - -TaskManager::TaskManager() { - // Get the number of available CPUs. This value does not change over time. - int cpuCount = sysconf(_SC_NPROCESSORS_CONF); - - // Really no point in making more than 2 of these worker threads, but - // we do want to limit ourselves to 1 worker thread on dual-core devices. - int workerCount = cpuCount > 2 ? 2 : 1; - for (int i = 0; i < workerCount; i++) { - String8 name; - name.appendFormat("hwuiTask%d", i + 1); - mThreads.push_back(new WorkerThread(name)); - } -} - -TaskManager::~TaskManager() { - for (size_t i = 0; i < mThreads.size(); i++) { - mThreads[i]->exit(); - } -} - -bool TaskManager::canRunTasks() const { - return mThreads.size() > 0; -} - -void TaskManager::stop() { - for (size_t i = 0; i < mThreads.size(); i++) { - mThreads[i]->exit(); - } -} - -bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) { - if (mThreads.size() > 0) { - TaskWrapper wrapper(task, processor); - - size_t minQueueSize = INT_MAX; - sp<WorkerThread> thread; - - for (size_t i = 0; i < mThreads.size(); i++) { - if (mThreads[i]->getTaskCount() < minQueueSize) { - thread = mThreads[i]; - minQueueSize = mThreads[i]->getTaskCount(); - } - } - - return thread->addTask(wrapper); - } - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// Thread -/////////////////////////////////////////////////////////////////////////////// - -status_t TaskManager::WorkerThread::readyToRun() { - setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND); - return NO_ERROR; -} - -bool TaskManager::WorkerThread::threadLoop() { - mSignal.wait(); - std::vector<TaskWrapper> tasks; - { - Mutex::Autolock l(mLock); - tasks.swap(mTasks); - } - - for (size_t i = 0; i < tasks.size(); i++) { - const TaskWrapper& task = tasks[i]; - task.mProcessor->process(task.mTask); - } - - return true; -} - -bool TaskManager::WorkerThread::addTask(const TaskWrapper& task) { - if (!isRunning()) { - run(mName.string(), PRIORITY_DEFAULT); - } else if (exitPending()) { - return false; - } - - { - Mutex::Autolock l(mLock); - mTasks.push_back(task); - } - mSignal.signal(); - - return true; -} - -size_t TaskManager::WorkerThread::getTaskCount() const { - Mutex::Autolock l(mLock); - return mTasks.size(); -} - -void TaskManager::WorkerThread::exit() { - requestExit(); - mSignal.signal(); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h deleted file mode 100644 index 29b4fcdbfde9..000000000000 --- a/libs/hwui/thread/TaskManager.h +++ /dev/null @@ -1,107 +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_TASK_MANAGER_H -#define ANDROID_HWUI_TASK_MANAGER_H - -#include <utils/Mutex.h> -#include <utils/String8.h> -#include <utils/Thread.h> - -#include "Signal.h" - -#include <vector> - -namespace android { -namespace uirenderer { - -template <typename T> -class Task; -class TaskBase; - -template <typename T> -class TaskProcessor; -class TaskProcessorBase; - -class TaskManager { -public: - TaskManager(); - ~TaskManager(); - - /** - * Returns true if this task manager can run tasks, - * false otherwise. This method will typically return - * false on a single CPU core device. - */ - bool canRunTasks() const; - - /** - * Stops all allocated threads. Adding tasks will start - * the threads again as necessary. - */ - void stop(); - -private: - template <typename T> - friend class TaskProcessor; - - template <typename T> - bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) { - return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor)); - } - - bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor); - - struct TaskWrapper { - TaskWrapper() : mTask(), mProcessor() {} - - TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) - : mTask(task), mProcessor(processor) {} - - sp<TaskBase> mTask; - sp<TaskProcessorBase> mProcessor; - }; - - class WorkerThread : public Thread { - public: - explicit WorkerThread(const String8& name) : mSignal(Condition::WAKE_UP_ONE), mName(name) {} - - bool addTask(const TaskWrapper& task); - size_t getTaskCount() const; - void exit(); - - private: - virtual status_t readyToRun() override; - virtual bool threadLoop() override; - - // Lock for the list of tasks - mutable Mutex mLock; - std::vector<TaskWrapper> mTasks; - - // Signal used to wake up the thread when a new - // task is available in the list - mutable Signal mSignal; - - const String8 mName; - }; - - std::vector<sp<WorkerThread> > mThreads; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TASK_MANAGER_H diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h deleted file mode 100644 index 8117ae6409c4..000000000000 --- a/libs/hwui/thread/TaskProcessor.h +++ /dev/null @@ -1,76 +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_TASK_PROCESSOR_H -#define ANDROID_HWUI_TASK_PROCESSOR_H - -#include <utils/RefBase.h> - -#include "Task.h" -#include "TaskManager.h" - -namespace android { -namespace uirenderer { - -class TaskProcessorBase : public RefBase { -public: - TaskProcessorBase() {} - virtual ~TaskProcessorBase(){}; - - virtual void process(const sp<TaskBase>& task) = 0; -}; - -template <typename T> -class TaskProcessor : public TaskProcessorBase { -public: - explicit TaskProcessor(TaskManager* manager) : mManager(manager) {} - virtual ~TaskProcessor() {} - - void add(const sp<Task<T> >& task) { - if (!addImpl(task)) { - // fall back to immediate execution - process(task); - } - } - - virtual void onProcess(const sp<Task<T> >& task) = 0; - -private: - bool addImpl(const sp<Task<T> >& task); - - virtual void process(const sp<TaskBase>& task) override { - sp<Task<T> > realTask = static_cast<Task<T>*>(task.get()); - // This is the right way to do it but sp<> doesn't play nice - // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task); - onProcess(realTask); - } - - TaskManager* mManager; -}; - -template <typename T> -bool TaskProcessor<T>::addImpl(const sp<Task<T> >& task) { - if (mManager) { - sp<TaskProcessor<T> > self(this); - return mManager->addTask(task, self); - } - return false; -} - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TASK_PROCESSOR_H diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h index 8068121f64cf..0289d3fd2ef7 100644 --- a/libs/hwui/thread/ThreadBase.h +++ b/libs/hwui/thread/ThreadBase.h @@ -27,7 +27,7 @@ namespace android::uirenderer { -class ThreadBase : protected Thread { +class ThreadBase : public Thread { PREVENT_COPY_AND_ASSIGN(ThreadBase); public: @@ -47,6 +47,8 @@ public: void join() { Thread::join(); } + bool isRunning() const { return Thread::isRunning(); } + protected: void waitForWork() { nsecs_t nextWakeup; @@ -66,10 +68,12 @@ protected: void processQueue() { mQueue.process(); } virtual bool threadLoop() override { + Looper::setForThread(mLooper); while (!exitPending()) { waitForWork(); processQueue(); } + Looper::setForThread(nullptr); return false; } diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h index 7a6e638e0568..42f8503fd000 100644 --- a/libs/hwui/thread/WorkQueue.h +++ b/libs/hwui/thread/WorkQueue.h @@ -26,7 +26,6 @@ #include <functional> #include <future> #include <mutex> -#include <variant> #include <vector> namespace android::uirenderer { diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp index 1bc5646993c9..763d1aa177b7 100644 --- a/libs/hwui/utils/Blur.cpp +++ b/libs/hwui/utils/Blur.cpp @@ -178,5 +178,5 @@ void Blur::vertical(float* weights, int32_t radius, const uint8_t* source, uint8 } } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h index bec3837106e8..d6b41b83def8 100644 --- a/libs/hwui/utils/Blur.h +++ b/libs/hwui/utils/Blur.h @@ -41,7 +41,7 @@ public: int32_t width, int32_t height); }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_HWUI_BLUR_H diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 75740e8b5baf..cc7725b7b9de 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -16,60 +16,67 @@ #include "Color.h" - #include <utils/Log.h> +#include <ui/ColorSpace.h> + +#include <algorithm> #include <cmath> namespace android { namespace uirenderer { -static inline bool almostEqual(float a, float b) { - return std::abs(a - b) < 1e-2f; +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: + ALOGV("Unsupported colorType: %d, return RGBA_8888 by default", (int)colorType); + return PIXEL_FORMAT_RGBA_8888; + } } -bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace) { - if (colorSpace == nullptr) return true; - if (colorSpace->isSRGB()) return true; - - SkColorSpaceTransferFn transferFunction; - if (colorSpace->isNumericalTransferFn(&transferFunction)) { - // sRGB transfer function params: - const float sRGBParamA = 1 / 1.055f; - const float sRGBParamB = 0.055f / 1.055f; - const float sRGBParamC = 1 / 12.92f; - const float sRGBParamD = 0.04045f; - const float sRGBParamE = 0.0f; - const float sRGBParamF = 0.0f; - const float sRGBParamG = 2.4f; - - // This comparison will catch Display P3 - return almostEqual(sRGBParamA, transferFunction.fA) && - almostEqual(sRGBParamB, transferFunction.fB) && - almostEqual(sRGBParamC, transferFunction.fC) && - almostEqual(sRGBParamD, transferFunction.fD) && - almostEqual(sRGBParamE, transferFunction.fE) && - almostEqual(sRGBParamF, transferFunction.fF) && - almostEqual(sRGBParamG, transferFunction.fG); +SkColorType PixelFormatToColorType(android::PixelFormat format) { + switch (format) { + case PIXEL_FORMAT_RGBX_8888: return kRGB_888x_SkColorType; + case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType; + case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; + case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType; + case PIXEL_FORMAT_RGBA_4444: return kARGB_4444_SkColorType; + default: + ALOGV("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format); + return kUnknown_SkColorType; } - - return false; } sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { + if (dataspace == HAL_DATASPACE_UNKNOWN) { + return SkColorSpace::MakeSRGB(); + } - SkColorSpace::Gamut gamut; + skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { case HAL_DATASPACE_STANDARD_BT709: - gamut = SkColorSpace::kSRGB_Gamut; + gamut = SkNamedGamut::kSRGB; break; case HAL_DATASPACE_STANDARD_BT2020: - gamut = SkColorSpace::kRec2020_Gamut; + gamut = SkNamedGamut::kRec2020; break; case HAL_DATASPACE_STANDARD_DCI_P3: - gamut = SkColorSpace::kDCIP3_D65_Gamut; + gamut = SkNamedGamut::kDCIP3; break; case HAL_DATASPACE_STANDARD_ADOBE_RGB: - gamut = SkColorSpace::kAdobeRGB_Gamut; + gamut = SkNamedGamut::kAdobeRGB; break; case HAL_DATASPACE_STANDARD_UNSPECIFIED: return nullptr; @@ -81,15 +88,15 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { case HAL_DATASPACE_STANDARD_BT470M: case HAL_DATASPACE_STANDARD_FILM: default: - ALOGW("Unsupported Gamut: %d", dataspace); + ALOGV("Unsupported Gamut: %d", dataspace); return nullptr; } switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) { case HAL_DATASPACE_TRANSFER_LINEAR: - return SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut); case HAL_DATASPACE_TRANSFER_SRGB: - return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, gamut); + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_2: return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut); case HAL_DATASPACE_TRANSFER_GAMMA2_6: @@ -102,10 +109,102 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { case HAL_DATASPACE_TRANSFER_ST2084: case HAL_DATASPACE_TRANSFER_HLG: default: - ALOGW("Unsupported Gamma: %d", dataspace); + ALOGV("Unsupported Gamma: %d", dataspace); return nullptr; } } -}; // namespace uirenderer -}; // namespace android +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..79400de08ee0 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -17,10 +17,13 @@ #define COLOR_H #include <math.h> +#include <cutils/compiler.h> #include <system/graphics.h> +#include <ui/PixelFormat.h> #include <SkColor.h> #include <SkColorSpace.h> +#include <SkImageInfo.h> namespace android { namespace uirenderer { @@ -79,17 +82,6 @@ static constexpr float OECF_sRGB(float linear) { return linear <= 0.0031308f ? linear * 12.92f : (powf(linear, 1.0f / 2.4f) * 1.055f) - 0.055f; } -// Opto-electronic conversion function for the sRGB color space -// Takes a linear sRGB value and converts it to a gamma-encoded sRGB value -// This function returns the input unmodified if linear blending is not enabled -static constexpr float OECF(float linear) { -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - return OECF_sRGB(linear); -#else - return linear; -#endif -} - // Electro-optical conversion function for the sRGB color space // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value static constexpr float EOCF_sRGB(float srgb) { @@ -97,23 +89,20 @@ static constexpr float EOCF_sRGB(float srgb) { return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); } -// Electro-optical conversion function for the sRGB color space -// Takes a gamma-encoded sRGB value and converts it to a linear sRGB value -// This function returns the input unmodified if linear blending is not enabled -static constexpr float EOCF(float srgb) { -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - return EOCF_sRGB(srgb); -#else - return srgb; -#endif -} +android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); +ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format); + +ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); + +struct Lab { + float L; + float a; + float b; +}; -// Returns whether the specified color space's transfer function can be -// approximated with the native sRGB transfer function. This method -// returns true for sRGB, gamma 2.2 and Display P3 for instance -bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); +Lab sRGBToLab(SkColor color); +SkColor LabToSRGB(const Lab& lab, SkAlpha alpha); -sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h index eafe2f13c16d..8cc4d1010ab6 100644 --- a/libs/hwui/utils/FatVector.h +++ b/libs/hwui/utils/FatVector.h @@ -99,7 +99,7 @@ private: typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_FAT_VECTOR_H diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp index bf27300029c6..c694e93f7e21 100644 --- a/libs/hwui/utils/GLUtils.cpp +++ b/libs/hwui/utils/GLUtils.cpp @@ -59,5 +59,22 @@ bool GLUtils::dumpGLErrors() { #endif } -}; // namespace uirenderer -}; // namespace android +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..8baa4b770f85 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() @@ -257,5 +249,5 @@ void LinearAllocator::dumpMemoryStats(const char* prefix) { ALOGD("%sPages %zu (dedicated %zu)", prefix, mPageCount, mDedicatedPageCount); } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index 16cf52f646b1..9c4a1be4b269 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -201,7 +201,7 @@ public: : std::vector<T, LinearStdAllocator<T>>(allocator) {} }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif // ANDROID_LINEARALLOCATOR_H 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/MathUtils.h b/libs/hwui/utils/MathUtils.h index 5475898bff28..cc8d83f10d43 100644 --- a/libs/hwui/utils/MathUtils.h +++ b/libs/hwui/utils/MathUtils.h @@ -34,6 +34,10 @@ public: return (value >= -NON_ZERO_EPSILON) && (value <= NON_ZERO_EPSILON); } + inline static bool isOne(float value) { + return areEqual(value, 1.0f); + } + inline static bool isPositive(float value) { return value >= NON_ZERO_EPSILON; } /** 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/Pair.h b/libs/hwui/utils/Pair.h index 4bcd57629e0c..76f93cbfeb92 100644 --- a/libs/hwui/utils/Pair.h +++ b/libs/hwui/utils/Pair.h @@ -36,7 +36,7 @@ struct Pair { inline const S& getSecond() const { return second; } }; -}; // namespace uirenderer +} // namespace uirenderer template <typename F, typename S> struct trait_trivial_ctor<uirenderer::Pair<F, S> > { @@ -55,6 +55,6 @@ struct trait_trivial_move<uirenderer::Pair<F, S> > { enum { value = aggregate_traits<F, S>::has_trivial_move }; }; -}; // namespace android +} // namespace android #endif // ANDROID_HWUI_PAIR_H diff --git a/libs/hwui/utils/Result.h b/libs/hwui/utils/Result.h new file mode 100644 index 000000000000..bd20ba66d8a7 --- /dev/null +++ b/libs/hwui/utils/Result.h @@ -0,0 +1,54 @@ +/* + * 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 <variant> +#include <log/log.h> + +namespace android::uirenderer { + +template <typename E> +struct Error { + E error; +}; + +template <typename R, typename E> +class Result { +public: + Result(const R& r) : result(std::forward<R>(r)) {} + Result(R&& r) : result(std::forward<R>(r)) {} + Result(Error<E>&& error) : result(std::forward<Error<E>>(error)) {} + + operator bool() const { + return result.index() == 0; + } + + R unwrap() const { + LOG_ALWAYS_FATAL_IF(result.index() == 1, "unwrap called on error value!"); + return std::get<R>(result); + } + + E error() const { + LOG_ALWAYS_FATAL_IF(result.index() == 0, "No error to get from Result"); + return std::get<Error<E>>(result).error; + } + +private: + std::variant<R, Error<E>> result; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h index b3e893139cf8..081386a7f671 100644 --- a/libs/hwui/utils/RingBuffer.h +++ b/libs/hwui/utils/RingBuffer.h @@ -61,7 +61,7 @@ private: size_t mCount = 0; }; -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // namespace android #endif /* RINGBUFFER_H_ */ diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp index 5304b762f3dc..304982e8e493 100644 --- a/libs/hwui/utils/StringUtils.cpp +++ b/libs/hwui/utils/StringUtils.cpp @@ -34,5 +34,5 @@ unordered_string_set StringUtils::split(const char* spacedList) { return set; } -}; // namespace uirenderer -}; // namespace android +} // namespace uirenderer +} // 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..1689ccecd6b1 --- /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 diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp index 0619a9c100dd..150f6dcde5d0 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -27,24 +27,29 @@ cc_library_shared { "libbinder", "liblog", "libutils", + "libprotobuf-cpp-lite", + ], + + static_libs: [ + "libplatformprotos", + ], + + whole_static_libs: [ + "libincidentcompanion", ], aidl: { - include_dirs: ["frameworks/base/core/java"], + include_dirs: [ + "frameworks/base/core/java", + "frameworks/native/libs/incidentcompanion/binder", + ], export_aidl_headers: true, }, srcs: [ ":libincident_aidl", - "proto/android/os/header.proto", - "proto/android/os/metadata.proto", "src/IncidentReportArgs.cpp", ], - proto: { - type: "lite", - export_proto_headers: true, - }, - export_include_dirs: ["include"], } diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h index 52b555e17ad5..94b4ad6eae31 100644 --- a/libs/incident/include/android/os/IncidentReportArgs.h +++ b/libs/incident/include/android/os/IncidentReportArgs.h @@ -24,19 +24,12 @@ #include <set> #include <vector> -#include "frameworks/base/libs/incident/proto/android/os/header.pb.h" - namespace android { namespace os { using namespace std; -// DESTINATION enum value, sync with proto/android/privacy.proto -const uint8_t DEST_LOCAL = 0; -const uint8_t DEST_EXPLICIT = 100; -const uint8_t DEST_AUTOMATIC = 200; - -// Aliases for the above. +// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto const uint8_t PRIVACY_POLICY_LOCAL = 0; const uint8_t PRIVACY_POLICY_EXPLICIT = 100; const uint8_t PRIVACY_POLICY_AUTOMATIC = 200; @@ -53,17 +46,18 @@ public: virtual status_t readFromParcel(const Parcel* in); void setAll(bool all); - void setDest(int dest); - void setPrivacyPolicy(int); + void setPrivacyPolicy(int privacyPolicy); void addSection(int section); - void setReceiverPkg(const string&); - void setReceiverCls(const string&); - void addHeader(const IncidentHeaderProto& headerProto); + void setReceiverPkg(const string& pkg); + void setReceiverCls(const string& cls); + void addHeader(const vector<uint8_t>& headerProto); inline bool all() const { return mAll; } - bool containsSection(int section) const; - inline int dest() const { return mDest; } + bool containsSection(int section, bool specific) const; + inline int getPrivacyPolicy() const { return mPrivacyPolicy; } inline const set<int>& sections() const { return mSections; } + inline const string& receiverPkg() const { return mReceiverPkg; } + inline const string& receiverCls() const { return mReceiverCls; } inline const vector<vector<uint8_t>>& headers() const { return mHeaders; } void merge(const IncidentReportArgs& that); @@ -72,7 +66,9 @@ private: set<int> mSections; vector<vector<uint8_t>> mHeaders; bool mAll; - int mDest; + int mPrivacyPolicy; + string mReceiverPkg; + string mReceiverCls; }; } diff --git a/libs/incident/proto/android/os/header.proto b/libs/incident/proto/android/os/header.proto deleted file mode 100644 index a84dc48b8b34..000000000000 --- a/libs/incident/proto/android/os/header.proto +++ /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. - */ - -syntax = "proto2"; -option java_multiple_files = true; - -package android.os; - -// IncidentHeaderProto contains extra information the caller of incidentd wants -// to attach in an incident report, the data should just be informative. -message IncidentHeaderProto { - // From statsd config, the id of the anomaly alert, unique among alerts. - optional int64 alert_id = 1; - - // Format a human readable reason why an incident report is requested. - // It's optional and may directly come from a user input clicking the - // bug-report button. - optional string reason = 2; - - // Defines which stats config used to fire the request, incident report will - // only be uploaded if this value is given. - message StatsdConfigKey { - optional int32 uid = 1; // The uid pushes the config to statsd. - optional int64 id = 2; // The unique id of the statsd config. - } - optional StatsdConfigKey config_key = 3; -} diff --git a/libs/incident/proto/android/os/metadata.proto b/libs/incident/proto/android/os/metadata.proto deleted file mode 100644 index f8f4e36b1e89..000000000000 --- a/libs/incident/proto/android/os/metadata.proto +++ /dev/null @@ -1,71 +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. - */ - -syntax = "proto2"; -option java_multiple_files = true; - -package android.os; - -// This field contains internal metadata associated with an incident report, -// such as the section ids and privacy policy specs from caller as well as how long -// and how many bytes a section takes, etc. -message IncidentMetadata { - - // The id of the incident report. - optional int64 report_id = 1; - - // The sequence number of the report. - optional int32 sequence_number = 2; - - // privacy level of the incident report. - enum Destination { - AUTOMATIC = 0; - EXPLICIT = 1; - LOCAL = 2; - } - optional Destination dest = 3; - - optional int32 request_size = 4; - - optional bool use_dropbox = 5; - - // stats of each section taken in this incident report. - message SectionStats { - // section id. - optional int32 id = 1; - // if the section is successfully taken. - optional bool success = 2; - // number of bytes in the report after filtering. - optional int32 report_size_bytes = 3; - // the total duration to execute the section. - optional int64 exec_duration_ms = 4; - - // number of bytes dumped from the section directly. - optional int32 dump_size_bytes = 5; - // duration of the dump takes. - optional int64 dump_duration_ms = 6; - // true if the section timed out. - optional bool timed_out = 7; - // true if the section is truncated. - optional bool is_truncated = 8; - - // Next Tag: 9 - } - repeated SectionStats sections = 6; - - // Next Tag: 7 -} - diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto deleted file mode 100644 index 1ef36df8121f..000000000000 --- a/libs/incident/proto/android/privacy.proto +++ /dev/null @@ -1,66 +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. - */ - -syntax = "proto2"; - -package android; - -option java_package = "com.android.incident"; -option java_multiple_files = true; - -import "google/protobuf/descriptor.proto"; - -enum Destination { - // Fields or messages annotated with DEST_LOCAL must never be - // extracted from the device automatically. They can be accessed - // by tools on the developer's workstation or test lab devices. - DEST_LOCAL = 0; - - // Fields or messages annotated with DEST_EXPLICIT can be sent - // off the device with an explicit user action. - DEST_EXPLICIT = 100; - - // Fields or messages annotated with DEST_AUTOMATIC can be sent by - // automatic means, without per-sending user consent. The user - // still must have previously accepted a consent to share this - // information. - DEST_AUTOMATIC = 200; - - // This is the default value, which could be overridden by other values. - // The reason to pick 255 is it fits into one byte. UNSET fields are treated - // as EXPLICIT. - DEST_UNSET = 255; - - // Currently use 0, 100, 200 and 255, values in between are reserved for futures. -} - -message PrivacyFlags { - optional Destination dest = 1 [ default = DEST_UNSET ]; - - // regex to filter pii sensitive info from a string field type. - repeated string patterns = 2; -} - -extend google.protobuf.FieldOptions { - // Flags used to annotate a field with right privacy level. - optional PrivacyFlags privacy = 102672883; -} - -extend google.protobuf.MessageOptions { - // Flags used to annotate a message which all its unset primitive fields inhert this tag. - optional PrivacyFlags msg_privacy = 102672883; -} - diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto deleted file mode 100644 index 45f3c91850e7..000000000000 --- a/libs/incident/proto/android/section.proto +++ /dev/null @@ -1,63 +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. - */ - -syntax = "proto2"; - -option java_package = "android"; -option java_multiple_files = true; - -import "google/protobuf/descriptor.proto"; - -package android; - -// SectionType defines how incidentd gonna get the field's data -enum SectionType { - - // Default fields, not available in incidentd - SECTION_NONE = 0; - - // incidentd reads a file to get the data for annotated field - SECTION_FILE = 1; - - // incidentd executes the given command for annotated field - SECTION_COMMAND = 2; - - // incidentd calls dumpsys for annotated field - SECTION_DUMPSYS = 3; - - // incidentd calls logs for annotated field - SECTION_LOG = 4; - - // incidentd read file and gzip the data in bytes field - SECTION_GZIP = 5; - - // incidentd calls tombstoned for annotated field - SECTION_TOMBSTONE = 6; -} - -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]; -} - -extend google.protobuf.FieldOptions { - // Flags for automatically section list generation - optional SectionFlags section = 155792027; -} diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp index fd0d5cc52db3..9d8a98338ef0 100644 --- a/libs/incident/src/IncidentReportArgs.cpp +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -18,7 +18,7 @@ #include <android/os/IncidentReportArgs.h> -#include <cutils/log.h> +#include <log/log.h> namespace android { namespace os { @@ -26,7 +26,7 @@ namespace os { IncidentReportArgs::IncidentReportArgs() :mSections(), mAll(false), - mDest(-1) + mPrivacyPolicy(-1) { } @@ -34,7 +34,9 @@ IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that) :mSections(that.mSections), mHeaders(that.mHeaders), mAll(that.mAll), - mDest(that.mDest) + mPrivacyPolicy(that.mPrivacyPolicy), + mReceiverPkg(that.mReceiverPkg), + mReceiverCls(that.mReceiverCls) { } @@ -76,7 +78,17 @@ IncidentReportArgs::writeToParcel(Parcel* out) const } } - err = out->writeInt32(mDest); + err = out->writeInt32(mPrivacyPolicy); + if (err != NO_ERROR) { + return err; + } + + err = out->writeString16(String16(mReceiverPkg.c_str())); + if (err != NO_ERROR) { + return err; + } + + err = out->writeString16(String16(mReceiverCls.c_str())); if (err != NO_ERROR) { return err; } @@ -127,12 +139,15 @@ IncidentReportArgs::readFromParcel(const Parcel* in) } } - int32_t dest; - err = in->readInt32(&dest); + int32_t privacyPolicy; + err = in->readInt32(&privacyPolicy); if (err != NO_ERROR) { return err; } - mDest = dest; + mPrivacyPolicy = privacyPolicy; + + mReceiverPkg = String8(in->readString16()).string(); + mReceiverCls = String8(in->readString16()).string(); return OK; } @@ -147,9 +162,9 @@ IncidentReportArgs::setAll(bool all) } void -IncidentReportArgs::setDest(int dest) +IncidentReportArgs::setPrivacyPolicy(int privacyPolicy) { - mDest = dest; + mPrivacyPolicy = privacyPolicy; } void @@ -161,55 +176,50 @@ IncidentReportArgs::addSection(int section) } void -IncidentReportArgs::addHeader(const IncidentHeaderProto& headerProto) -{ - vector<uint8_t> header; - auto serialized = headerProto.SerializeAsString(); - if (serialized.empty()) return; - for (auto it = serialized.begin(); it != serialized.end(); it++) { - header.push_back((uint8_t)*it); - } - mHeaders.push_back(header); -} - -bool -IncidentReportArgs::containsSection(int section) const +IncidentReportArgs::setReceiverPkg(const string& pkg) { - return mAll || mSections.find(section) != mSections.end(); + mReceiverPkg = pkg; } void -IncidentReportArgs::merge(const IncidentReportArgs& that) +IncidentReportArgs::setReceiverCls(const string& cls) { - if (mAll) { - return; - } else if (that.mAll) { - mAll = true; - mSections.clear(); - } else { - for (set<int>::const_iterator it=that.mSections.begin(); - it!=that.mSections.end(); it++) { - mSections.insert(*it); - } - } + mReceiverCls = cls; } -// stub void -IncidentReportArgs::setPrivacyPolicy(int) +IncidentReportArgs::addHeader(const vector<uint8_t>& headerProto) { + mHeaders.push_back(headerProto); } -// stub -void -IncidentReportArgs::setReceiverPkg(const string&) +bool +IncidentReportArgs::containsSection(int section, bool specific) const { + if (specific) { + return mSections.find(section) != mSections.end(); + } else { + return mAll || mSections.find(section) != mSections.end(); + } } -// stub void -IncidentReportArgs::setReceiverCls(const string&) +IncidentReportArgs::merge(const IncidentReportArgs& that) { + for (const vector<uint8_t>& header: that.mHeaders) { + mHeaders.push_back(header); + } + if (!mAll) { + if (that.mAll) { + mAll = true; + mSections.clear(); + } else { + for (set<int>::const_iterator it=that.mSections.begin(); + it!=that.mSections.end(); it++) { + mSections.insert(*it); + } + } + } } } diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 67682a06cb6a..89d3cc4f5083 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -14,7 +14,6 @@ cc_library_shared { name: "libinputservice", - srcs: [ "PointerController.cpp", "SpriteController.cpp", @@ -28,17 +27,19 @@ cc_library_shared { "libgui", "libui", "libinput", - "libinputflinger", "libnativewindow", ], + header_libs: [ + "libinputflinger_headers", + ], + include_dirs: ["frameworks/native/services"], cflags: [ "-Wall", + "-Wextra", "-Werror", - "-Wunused", - "-Wunreachable-code", ], } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 6c5f20c1d416..abf083789c23 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -89,10 +89,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.animationPending = false; - mLocked.displayWidth = -1; - mLocked.displayHeight = -1; - mLocked.displayOrientation = DISPLAY_ORIENTATION_0; - mLocked.presentation = PRESENTATION_POINTER; mLocked.presentationChanged = false; @@ -110,15 +106,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.lastFrameUpdatedTime = 0; mLocked.buttonState = 0; - - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - - loadResources(); - - if (mLocked.pointerIcon.isValid()) { - mLocked.pointerIconChanged = true; - updatePointerLocked(); - } } PointerController::~PointerController() { @@ -128,10 +115,14 @@ PointerController::~PointerController() { mLocked.pointerSprite.clear(); - for (size_t i = 0; i < mLocked.spots.size(); i++) { - delete mLocked.spots.itemAt(i); + for (auto& it : mLocked.spotsByDisplay) { + const std::vector<Spot*>& spots = it.second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots; i++) { + delete spots[i]; + } } - mLocked.spots.clear(); + mLocked.spotsByDisplay.clear(); mLocked.recycledSprites.clear(); } @@ -144,23 +135,15 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { - if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { + + if (!mLocked.viewport.isValid()) { return false; } - *outMinX = 0; - *outMinY = 0; - switch (mLocked.displayOrientation) { - case DISPLAY_ORIENTATION_90: - case DISPLAY_ORIENTATION_270: - *outMaxX = mLocked.displayHeight - 1; - *outMaxY = mLocked.displayWidth - 1; - break; - default: - *outMaxX = mLocked.displayWidth - 1; - *outMaxY = mLocked.displayHeight - 1; - break; - } + *outMinX = mLocked.viewport.logicalLeft; + *outMinY = mLocked.viewport.logicalTop; + *outMaxX = mLocked.viewport.logicalRight - 1; + *outMaxY = mLocked.viewport.logicalBottom - 1; return true; } @@ -231,6 +214,12 @@ void PointerController::getPosition(float* outX, float* outY) const { *outY = mLocked.pointerY; } +int32_t PointerController::getDisplayId() const { + AutoMutex _l(mLock); + + return mLocked.viewport.displayId; +} + void PointerController::fade(Transition transition) { AutoMutex _l(mLock); @@ -270,7 +259,7 @@ void PointerController::setPresentation(Presentation presentation) { if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources); + &mLocked.animationResources, mLocked.viewport.displayId); } if (mLocked.presentation != presentation) { @@ -286,22 +275,30 @@ void PointerController::setPresentation(Presentation presentation) { } void PointerController::setSpots(const PointerCoords* spotCoords, - const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { + const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { #if DEBUG_POINTER_UPDATES ALOGD("setSpots: idBits=%08x", spotIdBits.value); for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { uint32_t id = idBits.firstMarkedBit(); idBits.clearBit(id); const PointerCoords& c = spotCoords[spotIdToIndex[id]]; - ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id, + ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + displayId); } #endif AutoMutex _l(mLock); + std::vector<Spot*> newSpots; + std::map<int32_t, std::vector<Spot*>>::const_iterator iter = + mLocked.spotsByDisplay.find(displayId); + if (iter != mLocked.spotsByDisplay.end()) { + newSpots = iter->second; + } + mSpriteController->openTransaction(); // Add or move spots for fingers that are down. @@ -313,17 +310,17 @@ void PointerController::setSpots(const PointerCoords* spotCoords, float x = c.getAxisValue(AMOTION_EVENT_AXIS_X); float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y); - Spot* spot = getSpotLocked(id); + Spot* spot = getSpot(id, newSpots); if (!spot) { - spot = createAndAddSpotLocked(id); + spot = createAndAddSpotLocked(id, newSpots); } - spot->updateSprite(&icon, x, y); + spot->updateSprite(&icon, x, y, displayId); } // Remove spots for fingers that went up. - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); + for (size_t i = 0; i < newSpots.size(); i++) { + Spot* spot = newSpots[i]; if (spot->id != Spot::INVALID_ID && !spotIdBits.hasBit(spot->id)) { fadeOutAndReleaseSpotLocked(spot); @@ -331,6 +328,7 @@ void PointerController::setSpots(const PointerCoords* spotCoords, } mSpriteController->closeTransaction(); + mLocked.spotsByDisplay[displayId] = newSpots; } void PointerController::clearSpots() { @@ -355,48 +353,56 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout void PointerController::reloadPointerResources() { AutoMutex _l(mLock); - loadResources(); + loadResourcesLocked(); + updatePointerLocked(); +} - if (mLocked.presentation == PRESENTATION_POINTER) { - mLocked.additionalMouseResources.clear(); - mLocked.animationResources.clear(); - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources); - } +/** + * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation, + * so here we are getting the dimensions in the original, unrotated orientation (orientation 0). + */ +static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) { + width = viewport.deviceWidth; + height = viewport.deviceHeight; - mLocked.presentationChanged = true; - updatePointerLocked(); + if (viewport.orientation == DISPLAY_ORIENTATION_90 + || viewport.orientation == DISPLAY_ORIENTATION_270) { + std::swap(width, height); + } } -void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { +void PointerController::setDisplayViewport(const DisplayViewport& viewport) { AutoMutex _l(mLock); - - // Adjust to use the display's unrotated coordinate frame. - if (orientation == DISPLAY_ORIENTATION_90 - || orientation == DISPLAY_ORIENTATION_270) { - int32_t temp = height; - height = width; - width = temp; + if (viewport == mLocked.viewport) { + return; } - if (mLocked.displayWidth != width || mLocked.displayHeight != height) { - mLocked.displayWidth = width; - mLocked.displayHeight = height; + const DisplayViewport oldViewport = mLocked.viewport; + mLocked.viewport = viewport; + + int32_t oldDisplayWidth, oldDisplayHeight; + getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight); + int32_t newDisplayWidth, newDisplayHeight; + getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight); + + // Reset cursor position to center if size or display changed. + if (oldViewport.displayId != viewport.displayId + || oldDisplayWidth != newDisplayWidth + || oldDisplayHeight != newDisplayHeight) { float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { mLocked.pointerX = (minX + maxX) * 0.5f; mLocked.pointerY = (minY + maxY) * 0.5f; + // Reload icon resources for density may be changed. + loadResourcesLocked(); } else { mLocked.pointerX = 0; mLocked.pointerY = 0; } fadeOutAndReleaseAllSpotsLocked(); - } - - if (mLocked.displayOrientation != orientation) { + } else if (oldViewport.orientation != viewport.orientation) { // Apply offsets to convert from the pixel top-left corner position to the pixel center. // This creates an invariant frame of reference that we can easily rotate when // taking into account that the pointer may be located at fractional pixel offsets. @@ -405,37 +411,37 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ float temp; // Undo the previous rotation. - switch (mLocked.displayOrientation) { + switch (oldViewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; - x = mLocked.displayWidth - y; + x = oldViewport.deviceHeight - y; y = temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = oldViewport.deviceWidth - x; + y = oldViewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; x = y; - y = mLocked.displayHeight - temp; + y = oldViewport.deviceWidth - temp; break; } // Perform the new rotation. - switch (orientation) { + switch (viewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; x = y; - y = mLocked.displayWidth - temp; + y = viewport.deviceHeight - temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = viewport.deviceWidth - x; + y = viewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; - x = mLocked.displayHeight - y; + x = viewport.deviceWidth - y; y = temp; break; } @@ -444,7 +450,6 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ // and save the results. mLocked.pointerX = x - 0.5f; mLocked.pointerY = y - 0.5f; - mLocked.displayOrientation = orientation; } updatePointerLocked(); @@ -547,21 +552,33 @@ bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) { } // Animate spots that are fading out and being removed. - for (size_t i = 0; i < mLocked.spots.size();) { - Spot* spot = mLocked.spots.itemAt(i); - if (spot->id == Spot::INVALID_ID) { - spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; - if (spot->alpha <= 0) { - mLocked.spots.removeAt(i); - releaseSpotLocked(spot); - continue; - } else { - spot->sprite->setAlpha(spot->alpha); - keepAnimating = true; + for(auto it = mLocked.spotsByDisplay.begin(); it != mLocked.spotsByDisplay.end();) { + std::vector<Spot*>& spots = it->second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots;) { + Spot* spot = spots[i]; + if (spot->id == Spot::INVALID_ID) { + spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; + if (spot->alpha <= 0) { + spots.erase(spots.begin() + i); + releaseSpotLocked(spot); + numSpots--; + continue; + } else { + spot->sprite->setAlpha(spot->alpha); + keepAnimating = true; + } } + ++i; + } + + if (spots.size() == 0) { + it = mLocked.spotsByDisplay.erase(it); + } else { + ++it; } - ++i; } + return keepAnimating; } @@ -614,11 +631,16 @@ void PointerController::removeInactivityTimeoutLocked() { mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); } -void PointerController::updatePointerLocked() { +void PointerController::updatePointerLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mSpriteController->openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); @@ -658,47 +680,49 @@ void PointerController::updatePointerLocked() { mSpriteController->closeTransaction(); } -PointerController::Spot* PointerController::getSpotLocked(uint32_t id) { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); +PointerController::Spot* PointerController::getSpot(uint32_t id, const std::vector<Spot*>& spots) { + for (size_t i = 0; i < spots.size(); i++) { + Spot* spot = spots[i]; if (spot->id == id) { return spot; } } - return NULL; + + return nullptr; } -PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) { +PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id, + std::vector<Spot*>& spots) { // Remove spots until we have fewer than MAX_SPOTS remaining. - while (mLocked.spots.size() >= MAX_SPOTS) { - Spot* spot = removeFirstFadingSpotLocked(); + while (spots.size() >= MAX_SPOTS) { + Spot* spot = removeFirstFadingSpotLocked(spots); if (!spot) { - spot = mLocked.spots.itemAt(0); - mLocked.spots.removeAt(0); + spot = spots[0]; + spots.erase(spots.begin()); } releaseSpotLocked(spot); } // Obtain a sprite from the recycled pool. sp<Sprite> sprite; - if (! mLocked.recycledSprites.isEmpty()) { - sprite = mLocked.recycledSprites.top(); - mLocked.recycledSprites.pop(); + if (! mLocked.recycledSprites.empty()) { + sprite = mLocked.recycledSprites.back(); + mLocked.recycledSprites.pop_back(); } else { sprite = mSpriteController->createSprite(); } // Return the new spot. Spot* spot = new Spot(id, sprite); - mLocked.spots.push(spot); + spots.push_back(spot); return spot; } -PointerController::Spot* PointerController::removeFirstFadingSpotLocked() { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); +PointerController::Spot* PointerController::removeFirstFadingSpotLocked(std::vector<Spot*>& spots) { + for (size_t i = 0; i < spots.size(); i++) { + Spot* spot = spots[i]; if (spot->id == Spot::INVALID_ID) { - mLocked.spots.removeAt(i); + spots.erase(spots.begin() + i); return spot; } } @@ -709,7 +733,7 @@ void PointerController::releaseSpotLocked(Spot* spot) { spot->sprite->clearIcon(); if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { - mLocked.recycledSprites.push(spot->sprite); + mLocked.recycledSprites.push_back(spot->sprite); } delete spot; @@ -723,25 +747,40 @@ void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) { } void PointerController::fadeOutAndReleaseAllSpotsLocked() { - for (size_t i = 0; i < mLocked.spots.size(); i++) { - Spot* spot = mLocked.spots.itemAt(i); - fadeOutAndReleaseSpotLocked(spot); + for (auto& it : mLocked.spotsByDisplay) { + const std::vector<Spot*>& spots = it.second; + size_t numSpots = spots.size(); + for (size_t i = 0; i < numSpots; i++) { + Spot* spot = spots[i]; + fadeOutAndReleaseSpotLocked(spot); + } } } -void PointerController::loadResources() { - mPolicy->loadPointerResources(&mResources); +void PointerController::loadResourcesLocked() REQUIRES(mLock) { + mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId); + mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId); + + mLocked.additionalMouseResources.clear(); + mLocked.animationResources.clear(); + if (mLocked.presentation == PRESENTATION_POINTER) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources, mLocked.viewport.displayId); + } + + mLocked.pointerIconChanged = true; } // --- PointerController::Spot --- -void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) { +void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y, + int32_t displayId) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); sprite->setPosition(x, y); - + sprite->setDisplayId(displayId); this->x = x; this->y = y; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 4794f3da824c..52305b8244a6 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -23,12 +23,12 @@ #include <vector> #include <ui/DisplayInfo.h> +#include <input/DisplayViewport.h> #include <input/Input.h> -#include <inputflinger/PointerControllerInterface.h> +#include <PointerControllerInterface.h> #include <utils/BitSet.h> #include <utils/RefBase.h> #include <utils/Looper.h> -#include <utils/String8.h> #include <gui/DisplayEventReceiver.h> namespace android { @@ -62,10 +62,10 @@ protected: virtual ~PointerControllerPolicyInterface() { } public: - virtual void loadPointerIcon(SpriteIcon* icon) = 0; - virtual void loadPointerResources(PointerResources* outResources) = 0; + virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0; + virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0; virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, - std::map<int32_t, PointerAnimation>* outAnimationResources) = 0; + std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0; virtual int32_t getDefaultPointerIconId() = 0; virtual int32_t getCustomPointerIconId() = 0; }; @@ -97,17 +97,18 @@ public: virtual int32_t getButtonState() const; virtual void setPosition(float x, float y); virtual void getPosition(float* outX, float* outY) const; + virtual int32_t getDisplayId() const; virtual void fade(Transition transition); virtual void unfade(Transition transition); virtual void setPresentation(Presentation presentation); virtual void setSpots(const PointerCoords* spotCoords, - const uint32_t* spotIdToIndex, BitSet32 spotIdBits); + const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId); virtual void clearSpots(); void updatePointerIcon(int32_t iconId); void setCustomPointerIcon(const SpriteIcon& icon); - void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); + void setDisplayViewport(const DisplayViewport& viewport); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void reloadPointerResources(); @@ -132,7 +133,7 @@ private: : id(id), sprite(sprite), alpha(1.0f), scale(1.0f), x(0.0f), y(0.0f), lastIcon(NULL) { } - void updateSprite(const SpriteIcon* icon, float x, float y); + void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId); private: const SpriteIcon* lastIcon; @@ -157,9 +158,7 @@ private: size_t animationFrameIndex; nsecs_t lastFrameUpdatedTime; - int32_t displayWidth; - int32_t displayHeight; - int32_t displayOrientation; + DisplayViewport viewport; InactivityTimeout inactivityTimeout; @@ -181,9 +180,9 @@ private: int32_t buttonState; - Vector<Spot*> spots; - Vector<sp<Sprite> > recycledSprites; - } mLocked; + std::map<int32_t /* displayId */, std::vector<Spot*>> spotsByDisplay; + std::vector<sp<Sprite> > recycledSprites; + } mLocked GUARDED_BY(mLock); bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); @@ -201,14 +200,14 @@ private: void removeInactivityTimeoutLocked(); void updatePointerLocked(); - Spot* getSpotLocked(uint32_t id); - Spot* createAndAddSpotLocked(uint32_t id); - Spot* removeFirstFadingSpotLocked(); + Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots); + Spot* createAndAddSpotLocked(uint32_t id, std::vector<Spot*>& spots); + Spot* removeFirstFadingSpotLocked(std::vector<Spot*>& spots); void releaseSpotLocked(Spot* spot); void fadeOutAndReleaseSpotLocked(Spot* spot); void fadeOutAndReleaseAllSpotsLocked(); - void loadResources(); + void loadResourcesLocked(); }; } // namespace android diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 173cd507d943..c1868d3a94d6 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> @@ -148,13 +144,16 @@ void SpriteController::doUpdateSprites() { } } - // Resize sprites if needed. + // Resize and/or reparent sprites if needed. SurfaceComposerClient::Transaction t; bool needApplyTransaction = false; for (size_t i = 0; i < numSprites; i++) { SpriteUpdate& update = updates.editItemAt(i); + if (update.state.surfaceControl == nullptr) { + continue; + } - if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { + if (update.state.wantSurfaceVisible()) { int32_t desiredWidth = update.state.icon.bitmap.width(); int32_t desiredHeight = update.state.icon.bitmap.height(); if (update.state.surfaceWidth < desiredWidth @@ -174,6 +173,12 @@ void SpriteController::doUpdateSprites() { } } } + + // If surface is a new one, we have to set right layer stack. + if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) { + t.setLayerStack(update.state.surfaceControl, update.state.displayId); + needApplyTransaction = true; + } } if (needApplyTransaction) { t.apply(); @@ -240,7 +245,7 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -449,6 +454,15 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } +void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.displayId != displayId) { + mLocked.state.displayId = displayId; + invalidateLocked(DIRTY_DISPLAY_ID); + } +} + void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { bool wasDirty = mLocked.state.dirty; mLocked.state.dirty |= dirty; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 31e43e9b99e5..5b216f50d113 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -125,6 +125,9 @@ public: /* Sets the sprite transformation matrix. */ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; + + /* Sets the id of the display where the sprite should be shown. */ + virtual void setDisplayId(int32_t displayId) = 0; }; /* @@ -170,6 +173,7 @@ private: DIRTY_LAYER = 1 << 4, DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, + DIRTY_DISPLAY_ID = 1 << 7, }; /* Describes the state of a sprite. @@ -180,7 +184,7 @@ private: struct SpriteState { inline SpriteState() : dirty(0), visible(false), - positionX(0), positionY(0), layer(0), alpha(1.0f), + positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT), surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { } @@ -193,6 +197,7 @@ private: int32_t layer; float alpha; SpriteTransformationMatrix transformationMatrix; + int32_t displayId; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth; @@ -225,6 +230,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); + virtual void setDisplayId(int32_t displayId); inline const SpriteState& getStateLocked() const { return mLocked.state; diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 7ad83ca79695..b0af99732ddb 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", @@ -26,31 +25,46 @@ cc_library { srcs: [ "src/EncodedBuffer.cpp", + "src/ProtoFileReader.cpp", "src/ProtoOutputStream.cpp", + "src/ProtoReader.cpp", "src/protobuf.cpp", ], - export_include_dirs: ["include"], - shared_libs: [ + "libbase", + "libutils", "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/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h index 0b7f6e46be65..f9590ee7cd6d 100644 --- a/libs/protoutil/include/android/util/EncodedBuffer.h +++ b/libs/protoutil/include/android/util/EncodedBuffer.h @@ -17,6 +17,11 @@ #ifndef ANDROID_UTIL_ENCODED_BUFFER_H #define ANDROID_UTIL_ENCODED_BUFFER_H +#include <android/util/ProtoReader.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> + #include <stdint.h> #include <vector> @@ -34,12 +39,12 @@ namespace util { * *Index: Index of a buffer within the mBuffers list. * *Offset: Position within a buffer. */ -class EncodedBuffer +class EncodedBuffer : public virtual RefBase { public: EncodedBuffer(); explicit EncodedBuffer(size_t chunkSize); - ~EncodedBuffer(); + virtual ~EncodedBuffer(); class Pointer { public: @@ -80,8 +85,9 @@ public: Pointer* wp(); /** - * Returns the current position of write pointer, if the write buffer is full, it will automatically - * rotate to a new buffer with given chunkSize. If NULL is returned, it means NO_MEMORY + * Returns the current position of write pointer, if the write buffer is full, it will + * automatically rotate to a new buffer with given chunkSize. If NULL is returned, it + * means NO_MEMORY. */ uint8_t* writeBuffer(); @@ -120,6 +126,21 @@ public: */ size_t writeHeader(uint32_t fieldId, uint8_t wireType); + /** + * Copy the contents of the parameter into the write buffer. + */ + status_t writeRaw(uint8_t const* buf, size_t size); + + /** + * Copy the entire contents of the ProtoReader into the write buffer. + */ + status_t writeRaw(const sp<ProtoReader>& that); + + /** + * Copy the size bytes of contents of the ProtoReader into the write buffer. + */ + status_t writeRaw(const sp<ProtoReader>& that, size_t size); + /********************************* Edit APIs ************************************************/ /** * Returns the edit pointer. @@ -157,63 +178,35 @@ public: void copy(size_t srcPos, size_t size); /********************************* Read APIs ************************************************/ - class iterator; - friend class iterator; - class iterator { + /** + * Returns the Reader of EncodedBuffer so it guarantees consumers won't be able to + * modify the buffer. + */ + sp<ProtoReader> read(); + +private: + class Reader; + friend class Reader; + class Reader : public ProtoReader { public: - explicit iterator(const EncodedBuffer& buffer); - - /** - * Returns the number of bytes written in the buffer - */ - size_t size() const; - - /** - * Returns the size of total bytes read. - */ - size_t bytesRead() const; - - /** - * Returns the read pointer. - */ - Pointer* rp(); - - /** - * Returns the current position of read pointer, if NULL is returned, it reaches end of buffer. - */ - uint8_t const* readBuffer(); - - /** - * Returns the readable size in the current read buffer. - */ - size_t currentToRead(); - - /** - * Returns true if next bytes is available for read. - */ - bool hasNext(); - - /** - * Reads the current byte and moves pointer 1 bit. - */ - uint8_t next(); - - /** - * Read varint from iterator, the iterator will point to next available byte. - */ - uint64_t readRawVarint(); + explicit Reader(const sp<EncodedBuffer>& buffer); + virtual ~Reader(); + + virtual ssize_t size() const; + virtual size_t bytesRead() const; + virtual uint8_t const* readBuffer(); + virtual size_t currentToRead(); + virtual bool hasNext(); + virtual uint8_t next(); + virtual uint64_t readRawVarint(); + virtual void move(size_t amt); private: - const EncodedBuffer& mData; + const sp<EncodedBuffer> mData; Pointer mRp; + friend class EncodedBuffer; }; - /** - * Returns the iterator of EncodedBuffer so it guarantees consumers won't be able to modified the buffer. - */ - iterator begin() const; - -private: size_t mChunkSize; std::vector<uint8_t*> mBuffers; diff --git a/libs/protoutil/include/android/util/ProtoFileReader.h b/libs/protoutil/include/android/util/ProtoFileReader.h new file mode 100644 index 000000000000..cb3d012a9401 --- /dev/null +++ b/libs/protoutil/include/android/util/ProtoFileReader.h @@ -0,0 +1,75 @@ +/* + * 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 <cstdint> +#include <string> + +#include <android/util/EncodedBuffer.h> + +namespace android { +namespace util { + +/** + * A ProtoReader on top of a file descriptor. + */ +class ProtoFileReader : public ProtoReader +{ +public: + /** + * Read from this file descriptor. + */ + ProtoFileReader(int fd); + + /** + * Does NOT close the file. + */ + virtual ~ProtoFileReader(); + + // From ProtoReader. + virtual ssize_t size() const; + virtual size_t bytesRead() const; + virtual uint8_t const* readBuffer(); + virtual size_t currentToRead(); + virtual bool hasNext(); + virtual uint8_t next(); + virtual uint64_t readRawVarint(); + virtual void move(size_t amt); + + status_t getError() const; +private: + int mFd; // File descriptor for input. + status_t mStatus; // Any errors encountered during read. + ssize_t mSize; // How much total data there is, or -1 if we can't tell. + size_t mPos; // How much data has been read so far. + size_t mOffset; // Offset in current buffer. + size_t mMaxOffset; // How much data is left to read in mBuffer. + const int mChunkSize; // Size of mBuffer. + uint8_t mBuffer[32*1024]; + + /** + * If there is currently more data to read in the buffer, returns true. + * If there is not more, then tries to read. If more data can be read, + * it does so and returns true. If there is no more data, returns false. + * Resets mOffset and mMaxOffset as necessary. Does not advance mOffset. + */ + bool ensure_data(); +}; + +} +} + diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index e17f48b98037..a6af4757a140 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -17,9 +17,11 @@ #ifndef ANDROID_UTIL_PROTOOUTPUT_STREAM_H #define ANDROID_UTIL_PROTOOUTPUT_STREAM_H -#include <android/util/EncodedBuffer.h> - +#include <cstdint> #include <string> +#include <vector> + +#include <android/util/EncodedBuffer.h> namespace android { namespace util { @@ -104,7 +106,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); @@ -120,8 +122,10 @@ public: * it is not able to write to ProtoOutputStream any more since the data is compact. */ size_t size(); // Get the size of the serialized protobuf. - EncodedBuffer::iterator data(); // Get the reader apis of the data. + sp<ProtoReader> data(); // Get the reader apis of the data. bool flush(int fd); // Flush data directly to a file descriptor. + bool serializeToString(std::string* out); // Serializes the proto to a string. + bool serializeToVector(std::vector<uint8_t>* out); // Serializes the proto to a vector<uint8_t>. /** * Clears the ProtoOutputStream so the buffer can be reused instead of deallocation/allocation again. @@ -134,7 +138,7 @@ public: void writeRawByte(uint8_t byte); private: - EncodedBuffer mBuffer; + sp<EncodedBuffer> mBuffer; size_t mCopyBegin; bool mCompact; uint32_t mDepth; @@ -143,16 +147,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 +165,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/include/android/util/ProtoReader.h b/libs/protoutil/include/android/util/ProtoReader.h new file mode 100644 index 000000000000..204eb7dc9b0e --- /dev/null +++ b/libs/protoutil/include/android/util/ProtoReader.h @@ -0,0 +1,77 @@ +/* + * 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 <utils/Errors.h> +#include <utils/RefBase.h> + +#include <stdint.h> +#include <vector> + +namespace android { +namespace util { + +class ProtoReader : public virtual RefBase { +public: + ProtoReader(); + ~ProtoReader(); + + /** + * Returns the number of bytes written in the buffer + */ + virtual ssize_t size() const = 0; + + /** + * Returns the size of total bytes read. + */ + virtual size_t bytesRead() const = 0; + + /** + * Returns the current position of read pointer, if NULL is returned, it reaches + * end of buffer. + */ + virtual uint8_t const* readBuffer() = 0; + + /** + * Returns the readable size in the current read buffer. + */ + virtual size_t currentToRead() = 0; + + /** + * Returns true if next bytes is available for read. + */ + virtual bool hasNext() = 0; + + /** + * Reads the current byte and moves pointer 1 bit. + */ + virtual uint8_t next() = 0; + + /** + * Read varint from the reader, the reader will point to next available byte. + */ + virtual uint64_t readRawVarint() = 0; + + /** + * Advance the read pointer. + */ + virtual void move(size_t amt) = 0; +}; + +} // util +} // android + diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp index c017851a1623..7ffd8874a8fb 100644 --- a/libs/protoutil/src/EncodedBuffer.cpp +++ b/libs/protoutil/src/EncodedBuffer.cpp @@ -208,6 +208,63 @@ EncodedBuffer::writeHeader(uint32_t fieldId, uint8_t wireType) return writeRawVarint32((fieldId << FIELD_ID_SHIFT) | wireType); } +status_t +EncodedBuffer::writeRaw(uint8_t const* buf, size_t size) +{ + while (size > 0) { + uint8_t* target = writeBuffer(); + if (target == NULL) { + return -ENOMEM; + } + size_t chunk = currentToWrite(); + if (chunk > size) { + chunk = size; + } + memcpy(target, buf, chunk); + size -= chunk; + buf += chunk; + mWp.move(chunk); + } + return NO_ERROR; +} + +status_t +EncodedBuffer::writeRaw(const sp<ProtoReader>& reader) +{ + status_t err; + uint8_t const* buf; + while ((buf = reader->readBuffer()) != nullptr) { + size_t amt = reader->currentToRead(); + err = writeRaw(buf, amt); + reader->move(amt); + if (err != NO_ERROR) { + return err; + } + } + return NO_ERROR; +} + +status_t +EncodedBuffer::writeRaw(const sp<ProtoReader>& reader, size_t size) +{ + status_t err; + uint8_t const* buf; + while (size > 0 && (buf = reader->readBuffer()) != nullptr) { + size_t amt = reader->currentToRead(); + if (size < amt) { + amt = size; + } + err = writeRaw(buf, amt); + reader->move(amt); + size -= amt; + if (err != NO_ERROR) { + return err; + } + } + return size == 0 ? NO_ERROR : NOT_ENOUGH_DATA; +} + + /******************************** Edit APIs ************************************************/ EncodedBuffer::Pointer* EncodedBuffer::ep() @@ -283,66 +340,63 @@ EncodedBuffer::copy(size_t srcPos, size_t size) } /********************************* Read APIs ************************************************/ -EncodedBuffer::iterator -EncodedBuffer::begin() const +sp<ProtoReader> +EncodedBuffer::read() { - return EncodedBuffer::iterator(*this); + return new EncodedBuffer::Reader(this); } -EncodedBuffer::iterator::iterator(const EncodedBuffer& buffer) +EncodedBuffer::Reader::Reader(const sp<EncodedBuffer>& buffer) :mData(buffer), - mRp(buffer.mChunkSize) + mRp(buffer->mChunkSize) { } -size_t -EncodedBuffer::iterator::size() const -{ - return mData.size(); +EncodedBuffer::Reader::~Reader() { } -size_t -EncodedBuffer::iterator::bytesRead() const +ssize_t +EncodedBuffer::Reader::size() const { - return mRp.pos(); + return (ssize_t)mData->size(); } -EncodedBuffer::Pointer* -EncodedBuffer::iterator::rp() +size_t +EncodedBuffer::Reader::bytesRead() const { - return &mRp; + return mRp.pos(); } uint8_t const* -EncodedBuffer::iterator::readBuffer() +EncodedBuffer::Reader::readBuffer() { - return hasNext() ? const_cast<uint8_t const*>(mData.at(mRp)) : NULL; + return hasNext() ? const_cast<uint8_t const*>(mData->at(mRp)) : NULL; } size_t -EncodedBuffer::iterator::currentToRead() +EncodedBuffer::Reader::currentToRead() { - return (mData.mWp.index() > mRp.index()) ? - mData.mChunkSize - mRp.offset() : - mData.mWp.offset() - mRp.offset(); + return (mData->mWp.index() > mRp.index()) ? + mData->mChunkSize - mRp.offset() : + mData->mWp.offset() - mRp.offset(); } bool -EncodedBuffer::iterator::hasNext() +EncodedBuffer::Reader::hasNext() { - return mRp.pos() < mData.mWp.pos(); + return mRp.pos() < mData->mWp.pos(); } uint8_t -EncodedBuffer::iterator::next() +EncodedBuffer::Reader::next() { - uint8_t res = *(mData.at(mRp)); + uint8_t res = *(mData->at(mRp)); mRp.move(); return res; } uint64_t -EncodedBuffer::iterator::readRawVarint() +EncodedBuffer::Reader::readRawVarint() { uint64_t val = 0, shift = 0; while (true) { @@ -354,5 +408,11 @@ EncodedBuffer::iterator::readRawVarint() return val; } +void +EncodedBuffer::Reader::move(size_t amt) +{ + mRp.move(amt); +} + } // util } // android diff --git a/libs/protoutil/src/ProtoFileReader.cpp b/libs/protoutil/src/ProtoFileReader.cpp new file mode 100644 index 000000000000..bbb1fe374f0e --- /dev/null +++ b/libs/protoutil/src/ProtoFileReader.cpp @@ -0,0 +1,166 @@ +/* + * 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. + */ +#define LOG_TAG "libprotoutil" + +#include <android/util/ProtoFileReader.h> +#include <cutils/log.h> + +#include <cinttypes> +#include <type_traits> + +#include <unistd.h> + +namespace android { +namespace util { + +/** + * Get the amount of data remaining in the file in fd, or -1 if the file size can't be measured. + * It's not the whole file, but this allows us to skip any preamble that might have already + * been passed over. + */ +ssize_t get_file_size(int fd) { + off_t current = lseek(fd, 0, SEEK_CUR); + if (current < 0) { + return -1; + } + off_t end = lseek(fd, 0, SEEK_END); + if (end < 0) { + return -1; + } + off_t err = lseek(fd, current, SEEK_SET); + if (err < 0) { + ALOGW("get_file_size could do SEEK_END but not SEEK_SET. We might have skipped data."); + return -1; + } + return (ssize_t)(end-current); +} + +// ========================================================================= +ProtoFileReader::ProtoFileReader(int fd) + :mFd(fd), + mStatus(NO_ERROR), + mSize(get_file_size(fd)), + mPos(0), + mOffset(0), + mMaxOffset(0), + mChunkSize(sizeof(mBuffer)) { +} + +ProtoFileReader::~ProtoFileReader() { +} + +ssize_t +ProtoFileReader::size() const +{ + return (ssize_t)mSize; +} + +size_t +ProtoFileReader::bytesRead() const +{ + return mPos; +} + +uint8_t const* +ProtoFileReader::readBuffer() +{ + return hasNext() ? mBuffer + mOffset : NULL; +} + +size_t +ProtoFileReader::currentToRead() +{ + return mMaxOffset - mOffset; +} + +bool +ProtoFileReader::hasNext() +{ + return ensure_data(); +} + +uint8_t +ProtoFileReader::next() +{ + if (!ensure_data()) { + // Shouldn't get to here. Always call hasNext() before calling next(). + return 0; + } + return mBuffer[mOffset++]; +} + +uint64_t +ProtoFileReader::readRawVarint() +{ + uint64_t val = 0, shift = 0; + while (true) { + if (!hasNext()) { + ALOGW("readRawVarint() called without hasNext() called first."); + mStatus = NOT_ENOUGH_DATA; + return 0; + } + uint8_t byte = next(); + val |= (INT64_C(0x7F) & byte) << shift; + if ((byte & 0x80) == 0) break; + shift += 7; + } + return val; +} + +void +ProtoFileReader::move(size_t amt) +{ + while (mStatus == NO_ERROR && amt > 0) { + if (!ensure_data()) { + return; + } + const size_t chunk = + mMaxOffset - mOffset > amt ? amt : mMaxOffset - mOffset; + mOffset += chunk; + amt -= chunk; + } +} + +status_t +ProtoFileReader::getError() const { + return mStatus; +} + +bool +ProtoFileReader::ensure_data() { + if (mStatus != NO_ERROR) { + return false; + } + if (mOffset < mMaxOffset) { + return true; + } + ssize_t amt = TEMP_FAILURE_RETRY(read(mFd, mBuffer, mChunkSize)); + if (amt == 0) { + return false; + } else if (amt < 0) { + mStatus = -errno; + return false; + } else { + mOffset = 0; + mMaxOffset = amt; + return true; + } +} + + +} // util +} // android + diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index 22b9709ef066..6cfa357b580b 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> @@ -25,7 +27,7 @@ namespace android { namespace util { ProtoOutputStream::ProtoOutputStream() - :mBuffer(), + :mBuffer(new EncodedBuffer()), mCopyBegin(0), mCompact(false), mDepth(0), @@ -42,7 +44,7 @@ ProtoOutputStream::~ProtoOutputStream() void ProtoOutputStream::clear() { - mBuffer.clear(); + mBuffer->clear(); mCopyBegin = 0; mCompact = false; mDepth = 0; @@ -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; } } @@ -263,13 +226,13 @@ ProtoOutputStream::start(uint64_t fieldId) } uint32_t id = (uint32_t)fieldId; - size_t prevPos = mBuffer.wp()->pos(); - mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); - size_t sizePos = mBuffer.wp()->pos(); + size_t prevPos = mBuffer->wp()->pos(); + mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); + size_t sizePos = mBuffer->wp()->pos(); mDepth++; mObjectId++; - mBuffer.writeRawFixed64(mExpectedObjectToken); // push previous token into stack. + mBuffer->writeRawFixed64(mExpectedObjectToken); // push previous token into stack. mExpectedObjectToken = makeToken(sizePos - prevPos, (bool)(fieldId & FIELD_COUNT_REPEATED), mDepth, mObjectId, sizePos); @@ -281,68 +244,70 @@ 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--; uint32_t sizePos = getSizePosFromToken(token); // number of bytes written in this start-end session. - int childRawSize = mBuffer.wp()->pos() - sizePos - 8; + int childRawSize = mBuffer->wp()->pos() - sizePos - 8; // retrieve the old token from stack. - mBuffer.ep()->rewind()->move(sizePos); - mExpectedObjectToken = mBuffer.readRawFixed64(); + mBuffer->ep()->rewind()->move(sizePos); + mExpectedObjectToken = mBuffer->readRawFixed64(); // If raw size is larger than 0, write the negative value here to indicate a compact is needed. if (childRawSize > 0) { - mBuffer.editRawFixed32(sizePos, -childRawSize); - mBuffer.editRawFixed32(sizePos+4, -1); + mBuffer->editRawFixed32(sizePos, -childRawSize); + mBuffer->editRawFixed32(sizePos+4, -1); } else { // reset wp which erase the header tag of the message when its size is 0. - mBuffer.wp()->rewind()->move(sizePos - getTagSizeFromToken(token)); + mBuffer->wp()->rewind()->move(sizePos - getTagSizeFromToken(token)); } } size_t ProtoOutputStream::bytesWritten() { - return mBuffer.size(); + return mBuffer->size(); } 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. - size_t rawBufferSize = mBuffer.size(); + size_t rawBufferSize = mBuffer->size(); if (rawBufferSize == 0) return true; // nothing to do if the buffer is empty; // reset edit pointer and recursively compute encoded size of messages. - mBuffer.ep()->rewind(); + mBuffer->ep()->rewind(); if (editEncodedSize(rawBufferSize) == 0) { ALOGE("Failed to editEncodedSize."); return false; } // reset both edit pointer and write pointer, and compact recursively. - mBuffer.ep()->rewind(); - mBuffer.wp()->rewind(); + mBuffer->ep()->rewind(); + mBuffer->wp()->rewind(); if (!compactSize(rawBufferSize)) { ALOGE("Failed to compactSize."); return false; } // copy the reset to the buffer. if (mCopyBegin < rawBufferSize) { - mBuffer.copy(mCopyBegin, rawBufferSize - mCopyBegin); + mBuffer->copy(mCopyBegin, rawBufferSize - mCopyBegin); } // mark true means it is not legal to write to this ProtoOutputStream anymore @@ -357,34 +322,34 @@ ProtoOutputStream::compact() { size_t ProtoOutputStream::editEncodedSize(size_t rawSize) { - size_t objectStart = mBuffer.ep()->pos(); + size_t objectStart = mBuffer->ep()->pos(); size_t objectEnd = objectStart + rawSize; size_t encodedSize = 0; int childRawSize, childEncodedSize; size_t childEncodedSizePos; - while (mBuffer.ep()->pos() < objectEnd) { - uint32_t tag = (uint32_t)mBuffer.readRawVarint(); + while (mBuffer->ep()->pos() < objectEnd) { + uint32_t tag = (uint32_t)mBuffer->readRawVarint(); encodedSize += get_varint_size(tag); switch (read_wire_type(tag)) { case WIRE_TYPE_VARINT: do { encodedSize++; - } while ((mBuffer.readRawByte() & 0x80) != 0); + } while ((mBuffer->readRawByte() & 0x80) != 0); break; case WIRE_TYPE_FIXED64: encodedSize += 8; - mBuffer.ep()->move(8); + mBuffer->ep()->move(8); break; case WIRE_TYPE_LENGTH_DELIMITED: - childRawSize = (int)mBuffer.readRawFixed32(); - childEncodedSizePos = mBuffer.ep()->pos(); - childEncodedSize = (int)mBuffer.readRawFixed32(); + childRawSize = (int)mBuffer->readRawFixed32(); + childEncodedSizePos = mBuffer->ep()->pos(); + childEncodedSize = (int)mBuffer->readRawFixed32(); if (childRawSize >= 0 && childRawSize == childEncodedSize) { - mBuffer.ep()->move(childRawSize); + mBuffer->ep()->move(childRawSize); } else if (childRawSize < 0 && childEncodedSize == -1){ childEncodedSize = editEncodedSize(-childRawSize); - mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize); + mBuffer->editRawFixed32(childEncodedSizePos, childEncodedSize); } else { ALOGE("Bad raw or encoded values: raw=%d, encoded=%d at %zu", childRawSize, childEncodedSize, childEncodedSizePos); @@ -394,7 +359,7 @@ ProtoOutputStream::editEncodedSize(size_t rawSize) break; case WIRE_TYPE_FIXED32: encodedSize += 4; - mBuffer.ep()->move(4); + mBuffer->ep()->move(4); break; default: ALOGE("Unexpected wire type %d in editEncodedSize at [%zu, %zu]", @@ -413,30 +378,30 @@ ProtoOutputStream::editEncodedSize(size_t rawSize) bool ProtoOutputStream::compactSize(size_t rawSize) { - size_t objectStart = mBuffer.ep()->pos(); + size_t objectStart = mBuffer->ep()->pos(); size_t objectEnd = objectStart + rawSize; int childRawSize, childEncodedSize; - while (mBuffer.ep()->pos() < objectEnd) { - uint32_t tag = (uint32_t)mBuffer.readRawVarint(); + while (mBuffer->ep()->pos() < objectEnd) { + uint32_t tag = (uint32_t)mBuffer->readRawVarint(); switch (read_wire_type(tag)) { case WIRE_TYPE_VARINT: - while ((mBuffer.readRawByte() & 0x80) != 0) {} + while ((mBuffer->readRawByte() & 0x80) != 0) {} break; case WIRE_TYPE_FIXED64: - mBuffer.ep()->move(8); + mBuffer->ep()->move(8); break; case WIRE_TYPE_LENGTH_DELIMITED: - mBuffer.copy(mCopyBegin, mBuffer.ep()->pos() - mCopyBegin); + mBuffer->copy(mCopyBegin, mBuffer->ep()->pos() - mCopyBegin); - childRawSize = (int)mBuffer.readRawFixed32(); - childEncodedSize = (int)mBuffer.readRawFixed32(); - mCopyBegin = mBuffer.ep()->pos(); + childRawSize = (int)mBuffer->readRawFixed32(); + childEncodedSize = (int)mBuffer->readRawFixed32(); + mCopyBegin = mBuffer->ep()->pos(); // write encoded size to buffer. - mBuffer.writeRawVarint32(childEncodedSize); + mBuffer->writeRawVarint32(childEncodedSize); if (childRawSize >= 0 && childRawSize == childEncodedSize) { - mBuffer.ep()->move(childEncodedSize); + mBuffer->ep()->move(childEncodedSize); } else if (childRawSize < 0){ if (!compactSize(-childRawSize)) return false; } else { @@ -446,7 +411,7 @@ ProtoOutputStream::compactSize(size_t rawSize) } break; case WIRE_TYPE_FIXED32: - mBuffer.ep()->move(4); + mBuffer->ep()->move(4); break; default: ALOGE("Unexpected wire type %d in compactSize at [%zu, %zu]", @@ -462,67 +427,89 @@ ProtoOutputStream::size() { if (!compact()) { ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); - // TODO: handle this error + return 0; } - return mBuffer.size(); + return mBuffer->size(); } -static bool write_all(int fd, uint8_t const* buf, size_t size) +bool +ProtoOutputStream::flush(int fd) { - while (size > 0) { - ssize_t amt = ::write(fd, buf, size); - if (amt < 0) { + if (fd < 0) return false; + if (!compact()) return false; + + sp<ProtoReader> reader = mBuffer->read(); + while (reader->readBuffer() != NULL) { + if (!android::base::WriteFully(fd, reader->readBuffer(), reader->currentToRead())) { return false; } - size -= amt; - buf += amt; + reader->move(reader->currentToRead()); } return true; } bool -ProtoOutputStream::flush(int fd) +ProtoOutputStream::serializeToString(std::string* out) { - if (fd < 0) return false; + if (out == nullptr) return false; + if (!compact()) return false; + + sp<ProtoReader> reader = mBuffer->read(); + out->reserve(reader->size()); + while (reader->hasNext()) { + out->append(static_cast<const char*>(static_cast<const void*>(reader->readBuffer())), + reader->currentToRead()); + reader->move(reader->currentToRead()); + } + return true; +} + +bool +ProtoOutputStream::serializeToVector(std::vector<uint8_t>* out) +{ + if (out == nullptr) return false; if (!compact()) return false; - EncodedBuffer::iterator it = mBuffer.begin(); - while (it.readBuffer() != NULL) { - if (!write_all(fd, it.readBuffer(), it.currentToRead())) return false; - it.rp()->move(it.currentToRead()); + sp<ProtoReader> reader = mBuffer->read(); + out->reserve(reader->size()); + while (reader->hasNext()) { + const uint8_t* buf = reader->readBuffer(); + size_t size = reader->currentToRead(); + out->insert(out->end(), buf, buf + size); + reader->move(size); } return true; } -EncodedBuffer::iterator +sp<ProtoReader> ProtoOutputStream::data() { if (!compact()) { ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); - // TODO: handle this error + mBuffer->clear(); } - return mBuffer.begin(); + return mBuffer->read(); } void ProtoOutputStream::writeRawVarint(uint64_t varint) { - mBuffer.writeRawVarint64(varint); + mBuffer->writeRawVarint64(varint); } void ProtoOutputStream::writeLengthDelimitedHeader(uint32_t id, size_t size) { - mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); + mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); // reserves 64 bits for length delimited fields, if first field is negative, compact it. - mBuffer.writeRawFixed32(size); - mBuffer.writeRawFixed32(size); + mBuffer->writeRawFixed32(size); + mBuffer->writeRawFixed32(size); } void ProtoOutputStream::writeRawByte(uint8_t byte) { - mBuffer.writeRawByte(byte); + mBuffer->writeRawByte(byte); } @@ -542,99 +529,99 @@ inline To bit_cast(From const &from) { inline void ProtoOutputStream::writeDoubleImpl(uint32_t id, double val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val)); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED64); + mBuffer->writeRawFixed64(bit_cast<double, uint64_t>(val)); } inline void ProtoOutputStream::writeFloatImpl(uint32_t id, float val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val)); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED32); + mBuffer->writeRawFixed32(bit_cast<float, uint32_t>(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->writeHeader(id, WIRE_TYPE_VARINT); + 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->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val); } inline void ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint64(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint64(val); } inline void ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val); } inline void ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED64); + mBuffer->writeRawFixed64(val); } inline void ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED32); + mBuffer->writeRawFixed32(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->writeHeader(id, WIRE_TYPE_FIXED64); + 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->writeHeader(id, WIRE_TYPE_FIXED32); + 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)); + 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)); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32((val << 1) ^ (val >> 31)); } inline void ProtoOutputStream::writeEnumImpl(uint32_t id, int val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32((uint32_t) val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32((uint32_t) val); } inline void ProtoOutputStream::writeBoolImpl(uint32_t id, bool val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32(val ? 1 : 0); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val ? 1 : 0); } inline void @@ -643,7 +630,7 @@ ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size if (val == NULL) return; writeLengthDelimitedHeader(id, size); for (size_t i=0; i<size; i++) { - mBuffer.writeRawByte((uint8_t)val[i]); + mBuffer->writeRawByte((uint8_t)val[i]); } } @@ -653,7 +640,7 @@ ProtoOutputStream::writeMessageBytesImpl(uint32_t id, const char* val, size_t si if (val == NULL) return; writeLengthDelimitedHeader(id, size); for (size_t i=0; i<size; i++) { - mBuffer.writeRawByte(val[i]); + mBuffer->writeRawByte(val[i]); } } diff --git a/libs/hwui/renderstate/PixelBufferState.h b/libs/protoutil/src/ProtoReader.cpp index f7ae6c575f6a..4f2a9f1f5978 100644 --- a/libs/hwui/renderstate/PixelBufferState.h +++ b/libs/protoutil/src/ProtoReader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * 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. @@ -13,26 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef RENDERSTATE_PIXELBUFFERSTATE_H -#define RENDERSTATE_PIXELBUFFERSTATE_H +#define LOG_TAG "libprotoutil" -#include <GLES3/gl3.h> +#include <android/util/ProtoReader.h> namespace android { -namespace uirenderer { +namespace util { -class PixelBufferState { - friend class Caches; // TODO: move to RenderState -public: - bool bind(GLuint buffer); - bool unbind(); +ProtoReader::ProtoReader() { +} -private: - PixelBufferState(); - GLuint mCurrentPixelBuffer; -}; +ProtoReader::~ProtoReader() { +} -} /* namespace uirenderer */ -} /* namespace android */ - -#endif // RENDERSTATE_PIXELBUFFERSTATE_H +} // util +} // android diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp index 615ab4ab29ed..f895154c4983 100644 --- a/libs/protoutil/tests/EncodedBuffer_test.cpp +++ b/libs/protoutil/tests/EncodedBuffer_test.cpp @@ -16,10 +16,137 @@ #include <gtest/gtest.h> using namespace android::util; +using android::sp; + +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) { + sp<EncodedBuffer> buffer = new EncodedBuffer(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) { + sp<EncodedBuffer> buffer = new EncodedBuffer(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) { + sp<EncodedBuffer> buffer = new EncodedBuffer(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) { + sp<EncodedBuffer> buffer = new EncodedBuffer(TEST_CHUNK_SIZE); + for (size_t i = 0; i < TEST_CHUNK_3X_SIZE; i++) { + buffer->writeRawByte(i); + } + sp<ProtoReader> reader1 = buffer->read(); + EXPECT_EQ(reader1->size(), TEST_CHUNK_3X_SIZE); + EXPECT_EQ(reader1->bytesRead(), 0); + + while (reader1->readBuffer() != NULL) { + reader1->move(reader1->currentToRead()); + } + EXPECT_EQ(reader1->bytesRead(), TEST_CHUNK_3X_SIZE); + + sp<ProtoReader> reader2 = buffer->read(); + uint8_t val = 0; + while (reader2->hasNext()) { + EXPECT_EQ(reader2->next(), val); + val++; + } + EXPECT_EQ(reader2->bytesRead(), TEST_CHUNK_3X_SIZE); + EXPECT_EQ(reader1->bytesRead(), TEST_CHUNK_3X_SIZE); +} TEST(EncodedBufferTest, ReadVarint) { - EncodedBuffer buffer; + sp<EncodedBuffer> buffer = new EncodedBuffer(); uint64_t val = UINT64_C(1522865904593); - buffer.writeRawVarint64(val); - EXPECT_EQ(val, buffer.begin().readRawVarint()); + size_t len = buffer->writeRawVarint64(val); + sp<ProtoReader> reader = buffer->read(); + EXPECT_EQ(reader->size(), len); + EXPECT_EQ(reader->readRawVarint(), val); } diff --git a/libs/protoutil/tests/ProtoOutputStream_test.cpp b/libs/protoutil/tests/ProtoOutputStream_test.cpp new file mode 100644 index 000000000000..9b768b73d22b --- /dev/null +++ b/libs/protoutil/tests/ProtoOutputStream_test.cpp @@ -0,0 +1,358 @@ +// 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 android::sp; +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()); + sp<ProtoReader> reader = proto->data(); + while (reader->hasNext()) { + content.push_back(reader->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, SerializeToStringPrimitives) { + 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; + std::string serialized; + ASSERT_TRUE(proto.serializeToString(&serialized)); + ASSERT_TRUE(primitives.ParseFromString(serialized)); + 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, SerializeToVectorPrimitives) { + 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; + std::vector<uint8_t> vec; + ASSERT_TRUE(proto.serializeToVector(&vec)); + + std::string serialized(vec.data(), vec.data() + vec.size()); + ASSERT_TRUE(primitives.ParseFromString(serialized)); + + 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, SerializeToStringComplex) { + 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; + std::string serialized; + ASSERT_TRUE(proto.serializeToString(&serialized)); + ASSERT_TRUE(complex.ParseFromString(serialized)); + 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_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_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_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/Android.bp b/libs/services/Android.bp index 3d57fbdd0dcd..1b9939d9a598 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -18,6 +18,7 @@ cc_library_shared { name: "libservices", srcs: [ ":IDropBoxManagerService.aidl", + "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", "src/os/StatsDimensionsValue.cpp", "src/os/StatsLogEventWrapper.cpp", diff --git a/libs/services/include/android/content/ComponentName.h b/libs/services/include/android/content/ComponentName.h new file mode 100644 index 000000000000..6bf46b4bc28e --- /dev/null +++ b/libs/services/include/android/content/ComponentName.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <string> +#include <vector> + +namespace android { +namespace content { + +using namespace std; + +class ComponentName: public android::Parcelable { + public: + ComponentName(); + ComponentName(const ComponentName& that); + ComponentName(const string& pkg, const string& cls); + virtual ~ComponentName(); + + bool operator<(const ComponentName& that) const; + + const string& getPackageName() const { return mPackage; } + const string& getClassName() const { return mClass; } + + virtual android::status_t writeToParcel(android::Parcel* out) const override; + virtual android::status_t readFromParcel(const android::Parcel* in) override; + +private: + string mPackage; + string mClass; +}; + + + +} // namespace os +} // namespace android + + + diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h index 75b26c626d14..07472435d8a3 100644 --- a/libs/services/include/android/os/DropBoxManager.h +++ b/libs/services/include/android/os/DropBoxManager.h @@ -62,7 +62,7 @@ public: // file descriptor. Status addFile(const String16& tag, int fd, int flags); - class Entry : public virtual RefBase, public Parcelable { + class Entry : public Parcelable { public: Entry(); virtual ~Entry(); @@ -89,9 +89,6 @@ public: friend class DropBoxManager; }; - // Get the next entry from the drop box after the specified time. - Status getNextEntry(const String16& tag, long msec, Entry* entry); - private: enum { HAS_BYTE_ARRAY = 8 diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h index 255619c6226c..8de2ab49f42b 100644 --- a/libs/services/include/android/os/StatsLogEventWrapper.h +++ b/libs/services/include/android/os/StatsLogEventWrapper.h @@ -25,6 +25,68 @@ 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; +}; + +struct WorkChain { + std::vector<int32_t> uids; + std::vector<std::string> tags; +}; + // Represents a parcelable object. Only used to send data from Android OS to statsd. class StatsLogEventWrapper : public android::Parcelable { public: @@ -36,8 +98,26 @@ 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; } + + const std::vector<StatsLogValue>& getElements() const { return mElements; } + + const std::vector<WorkChain>& getWorkChains() const { return mWorkChains; } + + private: + int mTagId; + + int64_t mElapsedRealTimeNs; + + int64_t mWallClockTimeNs; + + std::vector<StatsLogValue> mElements; + + std::vector<WorkChain> mWorkChains; }; } // Namespace os } // Namespace android diff --git a/libs/services/src/content/ComponentName.cpp b/libs/services/src/content/ComponentName.cpp new file mode 100644 index 000000000000..adb67ee7c61a --- /dev/null +++ b/libs/services/src/content/ComponentName.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/content/ComponentName.h> + +namespace android { +namespace content { + +ComponentName::ComponentName() + :mPackage(), + mClass() { +} + +ComponentName::ComponentName(const ComponentName& that) + :mPackage(that.mPackage), + mClass(that.mClass) { +} + +ComponentName::ComponentName(const string& pkg, const string& cls) + :mPackage(pkg), + mClass(cls) { +} + +ComponentName::~ComponentName() { +} + +bool ComponentName::operator<(const ComponentName& that) const { + if (mPackage < that.mPackage) { + return true; + } else if (mPackage > that.mPackage) { + return false; + } + return mClass < that.mClass; +} + +status_t ComponentName::readFromParcel(const Parcel* in) { + status_t err; + + // Note: This is a subtle variation from the java version, which + // requires non-null strings, but does not require non-empty strings. + // This code implicitly requires non-null strings, because it's impossible, + // but reading null strings that were somehow written by the java + // code would turn them into empty strings. + + err = in->readUtf8FromUtf16(&mPackage); + if (err != NO_ERROR) { + return err; + } + + err = in->readUtf8FromUtf16(&mClass); + if (err != NO_ERROR) { + return err; + } + + return NO_ERROR; +} + +status_t ComponentName::writeToParcel(android::Parcel* out) const { + status_t err; + + err = out->writeUtf8AsUtf16(mPackage); + if (err != NO_ERROR) { + return err; + } + + err = out->writeUtf8AsUtf16(mClass); + if (err != NO_ERROR) { + return err; + } + + return NO_ERROR; +} + +}} // namespace android::content + diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index c2907a66fb99..429f996bd65e 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -225,18 +225,10 @@ DropBoxManager::add(const Entry& entry) if (service == NULL) { return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); } - return service->add(entry); -} - -Status -DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry) -{ - sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>( - defaultServiceManager()->getService(android::String16("dropbox"))); - if (service == NULL) { - return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); - } - return service->getNextEntry(tag, msec, entry); + ALOGD("About to call service->add()"); + Status status = service->add(entry); + ALOGD("service->add returned %s", status.toString8().string()); + return status; } }} // namespace android::os diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp index 8b3aa9ab4257..f6dfdef16e19 100644 --- a/libs/services/src/os/StatsLogEventWrapper.cpp +++ b/libs/services/src/os/StatsLogEventWrapper.cpp @@ -32,13 +32,95 @@ 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 numWorkChain = 0; + if ((res = in->readInt32(&numWorkChain)) != OK) { + ALOGE("statsd could not read number of work chains from parcel"); + return res; + } + if (numWorkChain > 0) { + for (int i = 0; i < numWorkChain; i++) { + int numNodes = 0; + if ((res = in->readInt32(&numNodes)) != OK) { + ALOGE( + "statsd could not read number of nodes in work chain from parcel"); + return res; + } + if (numNodes == 0) { + ALOGE("empty work chain"); + return BAD_VALUE; + } + WorkChain wc; + for (int j = 0; j < numNodes; j++) { + wc.uids.push_back(in->readInt32()); + wc.tags.push_back(std::string(String8(in->readString16()).string())); + } + mWorkChains.push_back(wc); + } + } + 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::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 |