| // Copyright 2018 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/service/service_font_manager.h" | |
| #include <inttypes.h> | |
| #include <bit> | |
| #include <type_traits> | |
| #include "base/bits.h" | |
| #include "base/check.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/debug/dump_without_crashing.h" | |
| #include "base/logging.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/memory/raw_span.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "base/rand_util.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "components/crash/core/common/crash_key.h" | |
| #include "gpu/command_buffer/common/buffer.h" | |
| #include "gpu/command_buffer/common/discardable_handle.h" | |
| namespace gpu { | |
| namespace { | |
| class Deserializer { | |
| public: | |
| explicit Deserializer(base::span<const volatile uint8_t> memory) | |
| : memory_(memory) {} | |
| ~Deserializer() = default; | |
| template <typename T> | |
| bool Read(T& val) { | |
| static_assert(std::is_trivially_copyable_v<T>); | |
| if (!AlignMemory(alignof(T))) { | |
| return false; | |
| } | |
| if (sizeof(T) > memory_.size()) { | |
| return false; | |
| } | |
| base::span<const volatile uint8_t> span = memory_.take_first(sizeof(T)); | |
| base::byte_span_from_ref(val).copy_from(span); | |
| return true; | |
| } | |
| bool ReadStrikeData(SkStrikeClient* strike_client, uint32_t size) { | |
| if (size == 0u) { | |
| return true; | |
| } | |
| if (!AlignMemory(16)) { | |
| return false; | |
| } | |
| if (size > memory_.size()) { | |
| return false; | |
| } | |
| auto strike_data = memory_.take_first(size); | |
| if (!strike_client->readStrikeData(strike_data.data(), | |
| strike_data.size())) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private: | |
| bool AlignMemory(size_t alignment) { | |
| // Due to the math below, alignment must be a power of two. | |
| DCHECK(std::has_single_bit(alignment)); | |
| uintptr_t addr = reinterpret_cast<uintptr_t>(memory_.data()); | |
| size_t padding = base::bits::AlignUp(addr, alignment) - addr; | |
| if (padding > memory_.size()) { | |
| return false; | |
| } | |
| memory_ = memory_.subspan(padding); | |
| return true; | |
| } | |
| base::raw_span<const volatile uint8_t> memory_; | |
| }; | |
| } // namespace | |
| class ServiceFontManager::SkiaDiscardableManager | |
| : public SkStrikeClient::DiscardableHandleManager { | |
| public: | |
| explicit SkiaDiscardableManager( | |
| scoped_refptr<ServiceFontManager> font_manager) | |
| : font_manager_(std::move(font_manager)) {} | |
| ~SkiaDiscardableManager() override = default; | |
| static constexpr int kMaxDumps = 5; | |
| bool deleteHandle(SkDiscardableHandleId handle_id) override { | |
| if (!font_manager_) | |
| return true; | |
| return font_manager_->DeleteHandle(handle_id); | |
| } | |
| void notifyCacheMiss(SkStrikeClient::CacheMissType type, | |
| int fontSize) override { | |
| // In general, Skia analysis of glyphs should find all cases. | |
| // If this is not happening, please file a bug with a repro so | |
| // it can be fixed. | |
| static crash_reporter::CrashKeyString<64> crash_key("oop_cache_miss"); | |
| static constexpr char kFormatString[] = "type: %" PRIu32 ", fontSize: %d"; | |
| #if DCHECK_IS_ON() | |
| crash_reporter::ScopedCrashKeyString auto_clear( | |
| &crash_key, base::StringPrintf(kFormatString, type, fontSize)); | |
| NOTREACHED(); | |
| #else | |
| if (dump_count_ < kMaxDumps && base::RandIntInclusive(1, 100) == 1 && | |
| !font_manager_->disable_oopr_debug_crash_dump()) { | |
| crash_reporter::ScopedCrashKeyString auto_clear( | |
| &crash_key, base::StringPrintf(kFormatString, type, fontSize)); | |
| base::debug::DumpWithoutCrashing(); | |
| ++dump_count_; | |
| } | |
| #endif | |
| } | |
| void notifyReadFailure( | |
| const DiscardableHandleManager::ReadFailureData& data) override { | |
| if (dump_count_ >= kMaxDumps) | |
| return; | |
| std::string str = base::StringPrintf( | |
| "ms: %zd, br: %zd, ts: %" PRIu64 ", sc: %" PRIu64 ", gic: %" PRIu64 | |
| ", gpc: %" PRIu64, | |
| data.memorySize, data.bytesRead, data.typefaceSize, data.strikeCount, | |
| data.glyphImagesCount, data.glyphPathsCount); | |
| static crash_reporter::CrashKeyString<128> crash_key("oop_read_failure"); | |
| crash_reporter::ScopedCrashKeyString auto_clear(&crash_key, str); | |
| ++dump_count_; | |
| base::debug::DumpWithoutCrashing(); | |
| } | |
| private: | |
| int dump_count_ = 0; | |
| scoped_refptr<ServiceFontManager> font_manager_; | |
| }; | |
| ServiceFontManager::ServiceFontManager(Client* client, | |
| bool disable_oopr_debug_crash_dump) | |
| : client_(client), | |
| client_thread_id_(base::PlatformThread::CurrentId()), | |
| strike_client_(std::make_unique<SkStrikeClient>( | |
| sk_make_sp<SkiaDiscardableManager>(this))), | |
| disable_oopr_debug_crash_dump_(disable_oopr_debug_crash_dump) {} | |
| ServiceFontManager::~ServiceFontManager() { | |
| DCHECK(destroyed_); | |
| DLOG_IF(ERROR, !discardable_handle_map_.empty()) | |
| << "discardable_handle_map_ is not empty."; | |
| } | |
| void ServiceFontManager::Destroy() { | |
| base::AutoLock hold(lock_); | |
| client_ = nullptr; | |
| strike_client_.reset(); | |
| destroyed_ = true; | |
| } | |
| bool ServiceFontManager::Deserialize( | |
| base::span<const volatile uint8_t> memory, | |
| std::vector<SkDiscardableHandleId>* locked_handles) { | |
| base::ReleasableAutoLock hold(&lock_); | |
| DCHECK_EQ(client_thread_id_, base::PlatformThread::CurrentId()); | |
| DCHECK(locked_handles->empty()); | |
| DCHECK(!destroyed_); | |
| // All new handles. | |
| Deserializer deserializer(memory); | |
| uint32_t new_handles_created; | |
| if (!deserializer.Read<uint32_t>(new_handles_created)) { | |
| return false; | |
| } | |
| for (uint32_t i = 0; i < new_handles_created; ++i) { | |
| SerializableSkiaHandle handle; | |
| if (!deserializer.Read<SerializableSkiaHandle>(handle)) { | |
| return false; | |
| } | |
| scoped_refptr<gpu::Buffer> buffer = client_->GetShmBuffer(handle.shm_id); | |
| if (!DiscardableHandleBase::ValidateParameters(buffer.get(), | |
| handle.byte_offset)) { | |
| return false; | |
| } | |
| if (!AddHandle(handle.handle_id, | |
| ServiceDiscardableHandle( | |
| std::move(buffer), handle.byte_offset, handle.shm_id))) { | |
| return false; | |
| } | |
| } | |
| // All locked handles | |
| uint32_t num_locked_handles; | |
| if (!deserializer.Read<uint32_t>(num_locked_handles)) { | |
| return false; | |
| } | |
| // Loosely avoid extremely large (but fake) numbers of locked handles. | |
| if (memory.size() / sizeof(SkDiscardableHandleId) < num_locked_handles) { | |
| return false; | |
| } | |
| locked_handles->resize(num_locked_handles); | |
| for (auto& locked_handle : *locked_handles) { | |
| if (!deserializer.Read<SkDiscardableHandleId>(locked_handle)) { | |
| return false; | |
| } | |
| auto it = discardable_handle_map_.find(locked_handle); | |
| if (it == discardable_handle_map_.end()) { | |
| DLOG(ERROR) << "Got an invalid SkDiscardableHandleId:" << locked_handle; | |
| continue; | |
| } | |
| it->second.Lock(); | |
| } | |
| // Skia font data. | |
| uint32_t skia_data_size = 0u; | |
| if (!deserializer.Read<uint32_t>(skia_data_size)) { | |
| return false; | |
| } | |
| hold.Release(); | |
| if (!deserializer.ReadStrikeData(strike_client_.get(), skia_data_size)) | |
| return false; | |
| return true; | |
| } | |
| bool ServiceFontManager::AddHandle(SkDiscardableHandleId handle_id, | |
| ServiceDiscardableHandle handle) { | |
| bool inserted; | |
| std::tie(std::ignore, inserted) = | |
| discardable_handle_map_.try_emplace(handle_id, std::move(handle)); | |
| return inserted; | |
| } | |
| bool ServiceFontManager::Unlock( | |
| const std::vector<SkDiscardableHandleId>& handles) { | |
| base::AutoLock hold(lock_); | |
| for (auto handle_id : handles) { | |
| auto it = discardable_handle_map_.find(handle_id); | |
| if (it == discardable_handle_map_.end()) | |
| return false; | |
| it->second.Unlock(); | |
| } | |
| return true; | |
| } | |
| bool ServiceFontManager::DeleteHandle(SkDiscardableHandleId handle_id) { | |
| base::AutoLock hold(lock_); | |
| // If this method returns true, the strike associated with the handle will be | |
| // deleted which deletes the memory for all glyphs cached by the strike. On | |
| // mac this is resulting in hangs during strike deserialization when a bunch | |
| // of strikes may be deleted in bulk. Try to avoid that by pinging the | |
| // progress reporter before deleting each strike. | |
| // Note that this method should generally only run on the Gpu main thread, | |
| // where skia is used, except for single process webview where the renderer | |
| // and GPU run in the same process. | |
| const bool report_progress = | |
| base::PlatformThread::CurrentId() == client_thread_id_ && !destroyed_; | |
| auto it = discardable_handle_map_.find(handle_id); | |
| if (it == discardable_handle_map_.end()) { | |
| LOG(ERROR) << "Tried to delete invalid SkDiscardableHandleId: " | |
| << handle_id; | |
| if (report_progress) { | |
| DCHECK(client_); | |
| client_->ReportProgress(); | |
| } | |
| return true; | |
| } | |
| // If the renderer is destroyed, we just need check if the local ref count is | |
| // 0. | |
| bool deleted = destroyed_ ? it->second.ref_count() == 0 : it->second.Delete(); | |
| if (!deleted) | |
| return false; | |
| DCHECK_EQ(it->second.ref_count(), 0); | |
| discardable_handle_map_.erase(it); | |
| if (report_progress) { | |
| DCHECK(client_); | |
| client_->ReportProgress(); | |
| } | |
| return true; | |
| } | |
| } // namespace gpu |