Commit a9d29a7a authored by Khushal's avatar Khushal Committed by Commit Bot

cc: Actively purge cached uploads for bitmaps from Canvas2D.

When using an unaccelerated canvas with a compositor using Gpu raster,
each canvas update is rasterized to a bitmap pushed into a paint
recording. This bitmap is uploaded and cached by cc's
GpuImageDecodeCache. Since the canvas is frequently updated, it is
common for the Gpu cache to use and keep the discardable texture memory
to capacity.

Avoid the above situation by actively deleting canvas snapshots in the
Gpu cache. PaintImages from a canvas instance use a stable
PaintImage::Id and update the PaintImage::ContentId when an updated
snapshot is created. This patch ensures that the Gpu cache keeps the
max 2 ContentId entries for the same PaintImage::Id (pending and active
tree), effectively ensuring we only have the last 2 canvas snapshots.

Note that BitmapImage, for encoded images in blink, also uses an updated
ContentId as more data is fetched for an image. This change will also
ensure that we evict previous partially decoded frames for the same
image.

For play:media:soundcloud test case on my linux machine, this reduced
cc's cache size from 236M to 30M.

R=enne@chromium.org, ericrk@chromium.org

Bug: 814219
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I888155ae5504ece6ab73981f320515478d87917d
Reviewed-on: https://chromium-review.googlesource.com/987194
Commit-Queue: Khushal <khushalsagar@chromium.org>
Reviewed-by: default avatarenne <enne@chromium.org>
Reviewed-by: default avatarEric Karl <ericrk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551078}
parent 6314d4ac
......@@ -53,6 +53,7 @@ class CC_PAINT_EXPORT PaintImage {
uint64_t hash() const { return hash_; }
std::string ToString() const;
size_t frame_index() const { return frame_index_; }
ContentId content_id() const { return content_id_; }
private:
ContentId content_id_;
......
......@@ -59,12 +59,15 @@ sk_sp<PaintImageGenerator> CreatePaintImageGenerator(const gfx::Size& size) {
PaintImage CreateDiscardablePaintImage(const gfx::Size& size,
sk_sp<SkColorSpace> color_space,
bool allocate_encoded_data) {
bool allocate_encoded_data,
PaintImage::Id id) {
if (!color_space)
color_space = SkColorSpace::MakeSRGB();
if (id == PaintImage::kInvalidId)
id = PaintImage::GetNextId();
return PaintImageBuilder::WithDefault()
.set_id(PaintImage::GetNextId())
.set_id(id)
.set_paint_image_generator(sk_make_sp<FakePaintImageGenerator>(
SkImageInfo::MakeN32Premul(size.width(), size.height(), color_space),
std::vector<FrameMetadata>{FrameMetadata()}, allocate_encoded_data))
......
......@@ -40,7 +40,8 @@ sk_sp<PaintImageGenerator> CreatePaintImageGenerator(const gfx::Size& size);
PaintImage CreateDiscardablePaintImage(
const gfx::Size& size,
sk_sp<SkColorSpace> color_space = nullptr,
bool allocate_encoded_memory = true);
bool allocate_encoded_memory = true,
PaintImage::Id id = PaintImage::kInvalidId);
DrawImage CreateDiscardableDrawImage(const gfx::Size& size,
sk_sp<SkColorSpace> color_space,
......
......@@ -861,16 +861,34 @@ void GpuImageDecodeCache::SetShouldAggressivelyFreeResources(
void GpuImageDecodeCache::ClearCache() {
base::AutoLock lock(lock_);
for (auto& entry : persistent_cache_) {
if (entry.second->decode.ref_count != 0 ||
entry.second->upload.ref_count != 0) {
// Orphan the entry so it will be deleted once no longer in use.
entry.second->is_orphaned = true;
} else if (entry.second->HasUploadedData()) {
DeleteImage(entry.second.get());
}
for (auto it = persistent_cache_.begin(); it != persistent_cache_.end();)
it = RemoveFromPersistentCache(it);
DCHECK(persistent_cache_.empty());
paint_image_entries_.clear();
}
GpuImageDecodeCache::PersistentCache::iterator
GpuImageDecodeCache::RemoveFromPersistentCache(PersistentCache::iterator it) {
lock_.AssertAcquired();
if (it->second->decode.ref_count != 0 || it->second->upload.ref_count != 0) {
// Orphan the image and erase it from the |persisent_cache_|. This ensures
// that the image will be deleted once all refs are removed.
it->second->is_orphaned = true;
} else {
// Current entry has no refs. Ensure it is not locked.
DCHECK(!it->second->decode.is_locked());
DCHECK(!it->second->upload.is_locked());
// Unlocked images must not be budgeted.
DCHECK(!it->second->is_budgeted);
// Free the uploaded image if it exists.
if (it->second->HasUploadedData())
DeleteImage(it->second.get());
}
persistent_cache_.Clear();
return persistent_cache_.Erase(it);
}
size_t GpuImageDecodeCache::GetMaximumMemoryLimitBytes() const {
......@@ -879,16 +897,11 @@ size_t GpuImageDecodeCache::GetMaximumMemoryLimitBytes() const {
void GpuImageDecodeCache::NotifyImageUnused(
const PaintImage::FrameKey& frame_key) {
base::AutoLock hold(lock_);
auto it = persistent_cache_.Peek(frame_key);
if (it != persistent_cache_.end()) {
if (it->second->decode.ref_count != 0 ||
it->second->upload.ref_count != 0) {
it->second->is_orphaned = true;
} else if (it->second->HasUploadedData()) {
DeleteImage(it->second.get());
}
persistent_cache_.Erase(it);
}
if (it != persistent_cache_.end())
RemoveFromPersistentCache(it);
}
bool GpuImageDecodeCache::OnMemoryDump(
......@@ -1499,6 +1512,7 @@ GpuImageDecodeCache::CreateImageData(const DrawImage& draw_image) {
"GpuImageDecodeCache::CreateImageData");
lock_.AssertAcquired();
WillAddCacheEntry(draw_image);
int mip_level = CalculateUploadScaleMipLevel(draw_image);
SkImageInfo image_info = CreateImageInfoForDrawImage(draw_image, mip_level);
......@@ -1527,6 +1541,49 @@ GpuImageDecodeCache::CreateImageData(const DrawImage& draw_image) {
CalculateDesiredFilterQuality(draw_image), mip_level, is_bitmap_backed));
}
void GpuImageDecodeCache::WillAddCacheEntry(const DrawImage& draw_image) {
lock_.AssertAcquired();
// Remove any old entries for this image. We keep at-most 2 ContentIds for a
// PaintImage (pending and active tree).
auto& cached_content_ids =
paint_image_entries_[draw_image.paint_image().stable_id()].content_ids;
const PaintImage::ContentId new_content_id =
draw_image.frame_key().content_id();
if (cached_content_ids[0] == new_content_id ||
cached_content_ids[1] == new_content_id) {
return;
}
if (cached_content_ids[0] == PaintImage::kInvalidContentId) {
cached_content_ids[0] = new_content_id;
return;
}
if (cached_content_ids[1] == PaintImage::kInvalidContentId) {
cached_content_ids[1] = new_content_id;
return;
}
const PaintImage::ContentId content_id_to_remove =
std::min(cached_content_ids[0], cached_content_ids[1]);
const PaintImage::ContentId content_id_to_keep =
std::max(cached_content_ids[0], cached_content_ids[1]);
DCHECK_NE(content_id_to_remove, content_id_to_keep);
for (auto it = persistent_cache_.begin(); it != persistent_cache_.end();) {
if (it->first.content_id() != content_id_to_remove) {
++it;
} else {
it = RemoveFromPersistentCache(it);
}
}
cached_content_ids[0] = content_id_to_keep;
cached_content_ids[1] = new_content_id;
}
void GpuImageDecodeCache::DeleteImage(ImageData* image_data) {
if (image_data->HasUploadedData()) {
DCHECK(!image_data->upload.is_locked());
......@@ -1737,6 +1794,12 @@ bool GpuImageDecodeCache::IsInInUseCacheForTesting(
return found != in_use_cache_.end();
}
bool GpuImageDecodeCache::IsInPersistentCacheForTesting(
const DrawImage& image) const {
auto found = persistent_cache_.Peek(image.frame_key());
return found != persistent_cache_.end();
}
sk_sp<SkImage> GpuImageDecodeCache::GetSWImageDecodeForTesting(
const DrawImage& image) {
base::AutoLock lock(lock_);
......
......@@ -160,6 +160,7 @@ class CC_EXPORT GpuImageDecodeCache
void SetImageDecodingFailedForTesting(const DrawImage& image);
bool DiscardableIsLockedForTesting(const DrawImage& image);
bool IsInInUseCacheForTesting(const DrawImage& image) const;
bool IsInPersistentCacheForTesting(const DrawImage& image) const;
sk_sp<SkImage> GetSWImageDecodeForTesting(const DrawImage& image);
private:
......@@ -385,6 +386,7 @@ class CC_EXPORT GpuImageDecodeCache
scoped_refptr<GpuImageDecodeCache::ImageData> CreateImageData(
const DrawImage& image);
void WillAddCacheEntry(const DrawImage& draw_image);
SkImageInfo CreateImageInfoForDrawImage(const DrawImage& draw_image,
int upload_scale_mip_level) const;
......@@ -425,6 +427,14 @@ class CC_EXPORT GpuImageDecodeCache
void CheckContextLockAcquiredIfNecessary();
// |persistent_cache_| represents the long-lived cache, keeping a certain
// budget of ImageDatas alive even when their ref count reaches zero.
using PersistentCache = base::HashingMRUCache<PaintImage::FrameKey,
scoped_refptr<ImageData>,
PaintImage::FrameKeyHash>;
PersistentCache::iterator RemoveFromPersistentCache(
PersistentCache::iterator it);
const SkColorType color_type_;
const bool use_transfer_cache_ = false;
viz::RasterContextProvider* context_;
......@@ -435,13 +445,16 @@ class CC_EXPORT GpuImageDecodeCache
// be accessed without a lock since they are thread safe.
base::Lock lock_;
// |persistent_cache_| represents the long-lived cache, keeping a certain
// budget of ImageDatas alive even when their ref count reaches zero.
using PersistentCache = base::HashingMRUCache<PaintImage::FrameKey,
scoped_refptr<ImageData>,
PaintImage::FrameKeyHash>;
PersistentCache persistent_cache_;
struct CacheEntries {
PaintImage::ContentId content_ids[2] = {PaintImage::kInvalidContentId,
PaintImage::kInvalidContentId};
};
// A map of PaintImage::Id to entries for this image in the
// |persistent_cache_|.
base::flat_map<PaintImage::Id, CacheEntries> paint_image_entries_;
// |in_use_cache_| represents the in-use (short-lived) cache. Entries are
// cleaned up as soon as their ref count reaches zero.
using InUseCache =
......
......@@ -2337,6 +2337,45 @@ TEST_P(GpuImageDecodeCacheTest, NonLazyImageUploadDownscaled) {
EXPECT_EQ(sw_image->height(), 5);
}
TEST_P(GpuImageDecodeCacheTest, KeepOnlyLast2ContentIds) {
auto cache = CreateCache();
bool is_decomposable = true;
SkFilterQuality quality = kHigh_SkFilterQuality;
viz::ContextProvider::ScopedContextLock context_lock(context_provider());
const PaintImage::Id paint_image_id = PaintImage::GetNextId();
std::vector<DrawImage> draw_images;
std::vector<DecodedDrawImage> decoded_draw_images;
for (int i = 0; i < 10; ++i) {
PaintImage image = CreateDiscardablePaintImage(
gfx::Size(10, 10), SkColorSpace::MakeSRGB(), true, paint_image_id);
DrawImage draw_image(
image, SkIRect::MakeWH(image.width(), image.height()), quality,
CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
PaintImage::kDefaultFrameIndex, DefaultColorSpace());
DecodedDrawImage decoded_draw_image =
EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
draw_images.push_back(draw_image);
decoded_draw_images.push_back(decoded_draw_image);
if (i == 0)
continue;
// We should only have the last 2 entries in the persistent cache, even
// though everything is in the in use cache.
EXPECT_EQ(cache->GetNumCacheEntriesForTesting(), 2u);
EXPECT_EQ(cache->GetInUseCacheEntriesForTesting(), i + 1u);
EXPECT_TRUE(cache->IsInPersistentCacheForTesting(draw_images[i]));
EXPECT_TRUE(cache->IsInPersistentCacheForTesting(draw_images[i - 1]));
}
for (int i = 0; i < 10; ++i) {
cache->DrawWithImageFinished(draw_images[i], decoded_draw_images[i]);
}
}
INSTANTIATE_TEST_CASE_P(
GpuImageDecodeCacheTests,
GpuImageDecodeCacheTest,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment