|
27 | 27 | #include "base/numerics/safe_math.h" |
28 | 28 | #include "base/strings/stringprintf.h" |
29 | 29 | #if BUILDFLAG(IS_COBALT) |
| 30 | +#include <list> |
| 31 | + |
| 32 | +#include "base/bits.h" |
| 33 | +#include "base/no_destructor.h" |
30 | 34 | #include "base/strings/string_number_conversions.h" |
31 | 35 | #endif |
32 | 36 | #include "base/synchronization/lock.h" |
@@ -456,6 +460,181 @@ sk_sp<SkImage> MakeTextureImage(viz::RasterContextProvider* context, |
456 | 460 | return uploaded_image; |
457 | 461 | } |
458 | 462 |
|
| 463 | +#if BUILDFLAG(IS_COBALT) |
| 464 | +// A simple arena allocator for image decodes. |
| 465 | +class ImageDecodeMemoryPool { |
| 466 | + public: |
| 467 | + static ImageDecodeMemoryPool* GetInstance() { |
| 468 | + static base::NoDestructor<ImageDecodeMemoryPool> instance; |
| 469 | + return instance.get(); |
| 470 | + } |
| 471 | + |
| 472 | + ImageDecodeMemoryPool() { |
| 473 | + // Size the pool to match the decoded image working set budget. |
| 474 | + pool_size_ = ImageDecodeCacheUtils::GetWorkingSetBytesForImageDecode( |
| 475 | + /*for_renderer=*/false); |
| 476 | + arena_ = std::make_unique<char[]>(pool_size_); |
| 477 | + blocks_.push_back({0, pool_size_, true}); |
| 478 | + LOG(INFO) << "COBALT_IMAGE_CACHE: POOL_CREATED: size=" << pool_size_; |
| 479 | + } |
| 480 | + |
| 481 | + ~ImageDecodeMemoryPool() = default; |
| 482 | + |
| 483 | + void* Allocate(size_t size, size_t* allocated_size) { |
| 484 | + base::AutoLock lock(mutex_); |
| 485 | + // Align size to 8 bytes using Chromium helper. |
| 486 | + size = base::bits::AlignUp<size_t>(size, 8); |
| 487 | + |
| 488 | + for (auto it = blocks_.begin(); it != blocks_.end(); ++it) { |
| 489 | + if (it->free && it->size >= size) { |
| 490 | + // Split block if it is larger than needed. |
| 491 | + if (it->size > size + 1024) { |
| 492 | + Block leftover = {it->offset + size, it->size - size, true}; |
| 493 | + it->size = size; |
| 494 | + it->free = false; |
| 495 | + blocks_.insert(std::next(it), leftover); |
| 496 | + } else { |
| 497 | + it->free = false; |
| 498 | + } |
| 499 | + *allocated_size = it->size; |
| 500 | + void* ptr = arena_.get() + it->offset; |
| 501 | + VLOG(2) << "COBALT_IMAGE_CACHE: ALLOC: size=" << size |
| 502 | + << ", actual=" << *allocated_size << ", offset=" << it->offset; |
| 503 | + total_allocs_++; |
| 504 | + return ptr; |
| 505 | + } |
| 506 | + } |
| 507 | + LOG(ERROR) << "COBALT_IMAGE_CACHE: POOL_OOM: size=" << size; |
| 508 | + total_fails_++; |
| 509 | + return nullptr; |
| 510 | + } |
| 511 | + |
| 512 | + void Free(void* ptr) { |
| 513 | + base::AutoLock lock(mutex_); |
| 514 | + if (!ptr) { |
| 515 | + return; |
| 516 | + } |
| 517 | + |
| 518 | + size_t offset = static_cast<char*>(ptr) - arena_.get(); |
| 519 | + for (auto it = blocks_.begin(); it != blocks_.end(); ++it) { |
| 520 | + if (it->offset == offset) { |
| 521 | + DCHECK(!it->free); |
| 522 | + it->free = true; |
| 523 | + VLOG(2) << "COBALT_IMAGE_CACHE: FREE: offset=" << offset |
| 524 | + << ", size=" << it->size; |
| 525 | + Coalesce(); |
| 526 | + return; |
| 527 | + } |
| 528 | + } |
| 529 | + NOTREACHED() |
| 530 | + << "COBALT_IMAGE_CACHE: Attempted to free pointer not in pool: " << ptr; |
| 531 | + } |
| 532 | + |
| 533 | + size_t GetPoolSize() const { return pool_size_; } |
| 534 | + |
| 535 | + struct PoolStats { |
| 536 | + size_t total_allocs; |
| 537 | + size_t total_fails; |
| 538 | + size_t free_size; |
| 539 | + size_t pool_size; |
| 540 | + size_t min_free_block_size; |
| 541 | + size_t max_free_block_size; |
| 542 | + }; |
| 543 | + PoolStats GetStats() { |
| 544 | + base::AutoLock lock(mutex_); |
| 545 | + size_t free_size = 0; |
| 546 | + size_t min_free = pool_size_; |
| 547 | + size_t max_free = 0; |
| 548 | + bool has_free = false; |
| 549 | + for (const auto& block : blocks_) { |
| 550 | + if (block.free) { |
| 551 | + has_free = true; |
| 552 | + free_size += block.size; |
| 553 | + if (block.size < min_free) { |
| 554 | + min_free = block.size; |
| 555 | + } |
| 556 | + if (block.size > max_free) { |
| 557 | + max_free = block.size; |
| 558 | + } |
| 559 | + } |
| 560 | + } |
| 561 | + if (!has_free) { |
| 562 | + min_free = 0; |
| 563 | + max_free = 0; |
| 564 | + } |
| 565 | + return {total_allocs_, total_fails_, free_size, |
| 566 | + pool_size_, min_free, max_free}; |
| 567 | + } |
| 568 | + |
| 569 | + private: |
| 570 | + struct Block { |
| 571 | + size_t offset; |
| 572 | + size_t size; |
| 573 | + bool free; |
| 574 | + }; |
| 575 | + |
| 576 | + void Coalesce() { |
| 577 | + auto it = blocks_.begin(); |
| 578 | + while (it != blocks_.end() && std::next(it) != blocks_.end()) { |
| 579 | + auto next = std::next(it); |
| 580 | + if (it->free && next->free) { |
| 581 | + it->size += next->size; |
| 582 | + blocks_.erase(next); |
| 583 | + } else { |
| 584 | + ++it; |
| 585 | + } |
| 586 | + } |
| 587 | + } |
| 588 | + |
| 589 | + size_t pool_size_; |
| 590 | + std::unique_ptr<char[]> arena_; |
| 591 | + std::list<Block> blocks_; |
| 592 | + base::Lock mutex_; |
| 593 | + size_t total_allocs_ = 0; |
| 594 | + size_t total_fails_ = 0; |
| 595 | +}; |
| 596 | + |
| 597 | +class PooledDiscardableMemory : public base::DiscardableMemory { |
| 598 | + public: |
| 599 | + explicit PooledDiscardableMemory(size_t size) : size_(size) { |
| 600 | + data_ = |
| 601 | + ImageDecodeMemoryPool::GetInstance()->Allocate(size, &allocated_size_); |
| 602 | + } |
| 603 | + |
| 604 | + ~PooledDiscardableMemory() override { |
| 605 | + if (data_) { |
| 606 | + ImageDecodeMemoryPool::GetInstance()->Free(data_); |
| 607 | + } |
| 608 | + } |
| 609 | + |
| 610 | + [[nodiscard]] bool Lock() override { return data_ != nullptr; } |
| 611 | + |
| 612 | + void Unlock() override { |
| 613 | + // Keep locked until destroyed. |
| 614 | + } |
| 615 | + |
| 616 | + void* data() const override { return data_; } |
| 617 | + |
| 618 | + void DiscardForTesting() override { |
| 619 | + // Not implemented. |
| 620 | + } |
| 621 | + |
| 622 | + base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump( |
| 623 | + const char* name, |
| 624 | + base::trace_event::ProcessMemoryDump* pmd) const override { |
| 625 | + auto* dump = pmd->CreateAllocatorDump(name); |
| 626 | + dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| 627 | + base::trace_event::MemoryAllocatorDump::kUnitsBytes, size_); |
| 628 | + return dump; |
| 629 | + } |
| 630 | + |
| 631 | + private: |
| 632 | + void* data_ = nullptr; |
| 633 | + size_t size_; |
| 634 | + size_t allocated_size_ = 0; |
| 635 | +}; |
| 636 | +#endif // BUILDFLAG(IS_COBALT) |
| 637 | + |
459 | 638 | // We use this below, instead of just a std::unique_ptr, so that we can run |
460 | 639 | // a Finch experiment to check the impact of not using discardable memory on the |
461 | 640 | // GPU decode path. |
@@ -2379,6 +2558,26 @@ bool GpuImageDecodeCache::ExceedsCacheLimits() const { |
2379 | 2558 | #endif |
2380 | 2559 | } |
2381 | 2560 |
|
| 2561 | +#if BUILDFLAG(IS_COBALT) |
| 2562 | + static int call_count = 0; |
| 2563 | + if (++call_count % 100 == 0) { |
| 2564 | + auto pool_stats = ImageDecodeMemoryPool::GetInstance()->GetStats(); |
| 2565 | + LOG(INFO) << "COBALT_IMAGE_CACHE: STATS: items=" << persistent_cache_.size() |
| 2566 | + << ", limit=" << items_limit |
| 2567 | + << ", pool_allocs=" << pool_stats.total_allocs |
| 2568 | + << ", pool_fails=" << pool_stats.total_fails |
| 2569 | + << ", pool_free=" << pool_stats.free_size |
| 2570 | + << ", pool_size=" << pool_stats.pool_size |
| 2571 | + << ", min_block_kb=" << (pool_stats.min_free_block_size / 1024) |
| 2572 | + << ", max_block_kb=" << (pool_stats.max_free_block_size / 1024); |
| 2573 | + } |
| 2574 | +#else |
| 2575 | + static int call_count = 0; |
| 2576 | + if (++call_count % 100 == 0) { |
| 2577 | + LOG(INFO) << "COBALT_IMAGE_CACHE: STATS: items=" << persistent_cache_.size() |
| 2578 | + << ", limit=" << items_limit; |
| 2579 | + } |
| 2580 | +#endif |
2382 | 2581 | return persistent_cache_.size() > items_limit; |
2383 | 2582 | } |
2384 | 2583 |
|
@@ -2509,15 +2708,35 @@ void GpuImageDecodeCache::DecodeImageIfNecessary( |
2509 | 2708 |
|
2510 | 2709 | // Allocate the backing memory for the decode. |
2511 | 2710 | std::unique_ptr<base::DiscardableMemory> backing_memory; |
2512 | | - if (base::FeatureList::IsEnabled( |
2513 | | - features::kNoDiscardableMemoryForGpuDecodePath)) { |
2514 | | - backing_memory = std::make_unique<HeapDiscardableMemory>(info.size); |
2515 | | - } else { |
2516 | | - auto* allocator = base::DiscardableMemoryAllocator::GetInstance(); |
2517 | | - backing_memory = |
2518 | | - allocator->AllocateLockedDiscardableMemoryWithRetryOrDie( |
2519 | | - info.size, base::BindOnce(&GpuImageDecodeCache::ClearCache, |
2520 | | - base::Unretained(this))); |
| 2711 | +#if BUILDFLAG(IS_COBALT) |
| 2712 | + static const bool use_pooled_memory = |
| 2713 | + base::FeatureList::IsEnabled(features::kPreallocatedImageCachePool); |
| 2714 | + if (use_pooled_memory) { |
| 2715 | + auto pooled_memory = |
| 2716 | + std::make_unique<PooledDiscardableMemory>(info.size); |
| 2717 | + if (!pooled_memory->Lock()) { |
| 2718 | + ClearCache(); |
| 2719 | + pooled_memory = std::make_unique<PooledDiscardableMemory>(info.size); |
| 2720 | + if (!pooled_memory->Lock()) { |
| 2721 | + LOG(WARNING) << "COBALT_IMAGE_CACHE: POOL_FALLBACK: size=" |
| 2722 | + << info.size; |
| 2723 | + pooled_memory.reset(); |
| 2724 | + } |
| 2725 | + } |
| 2726 | + backing_memory = std::move(pooled_memory); |
| 2727 | + } |
| 2728 | +#endif |
| 2729 | + if (!backing_memory) { |
| 2730 | + if (base::FeatureList::IsEnabled( |
| 2731 | + features::kNoDiscardableMemoryForGpuDecodePath)) { |
| 2732 | + backing_memory = std::make_unique<HeapDiscardableMemory>(info.size); |
| 2733 | + } else { |
| 2734 | + auto* allocator = base::DiscardableMemoryAllocator::GetInstance(); |
| 2735 | + backing_memory = |
| 2736 | + allocator->AllocateLockedDiscardableMemoryWithRetryOrDie( |
| 2737 | + info.size, base::BindOnce(&GpuImageDecodeCache::ClearCache, |
| 2738 | + base::Unretained(this))); |
| 2739 | + } |
2521 | 2740 | } |
2522 | 2741 |
|
2523 | 2742 | // Do the decode. |
@@ -2591,6 +2810,10 @@ void GpuImageDecodeCache::DecodeImageIfNecessary( |
2591 | 2810 |
|
2592 | 2811 | image_data->decode.SetLockedData(aux_image_data, |
2593 | 2812 | task_type == TaskType::kOutOfRaster); |
| 2813 | + LOG(INFO) << "COBALT_IMAGE_CACHE: SW_DECODE: id=" |
| 2814 | + << image_data->paint_image_id |
| 2815 | + << ", size=" << draw_image.paint_image().width() << "x" |
| 2816 | + << draw_image.paint_image().height(); |
2594 | 2817 | } |
2595 | 2818 |
|
2596 | 2819 | void GpuImageDecodeCache::GenerateDarkModeFilter(const DrawImage& draw_image, |
@@ -2728,6 +2951,9 @@ void GpuImageDecodeCache::UploadImageIfNecessary_TransferCache_HardwareDecode( |
2728 | 2951 | draw_image.paint_image().GetSwSkImage()->refEncodedData(); |
2729 | 2952 | DCHECK(encoded_data); |
2730 | 2953 | const uint32_t transfer_cache_id = ClientImageTransferCacheEntry::GetNextId(); |
| 2954 | + LOG(INFO) << "COBALT_IMAGE_CACHE: HW_DECODE: id=" |
| 2955 | + << image_data->paint_image_id << ", size=" << output_size.ToString() |
| 2956 | + << ", encoded_size=" << encoded_data->size(); |
2731 | 2957 | const gpu::SyncToken decode_sync_token = |
2732 | 2958 | context_->RasterInterface()->ScheduleImageDecode( |
2733 | 2959 | gfx::SkDataToSpan(encoded_data), output_size, transfer_cache_id, |
|
0 commit comments