LibGfx: Make Skia image cache eviction pressure-based

DecodedImageFrameSkiaImageCache used to treat each display-list
flush as a cache generation. That worked poorly for the compositor,
where all contexts share one DisplayListPlayerSkia and therefore one
decoded-frame cache. A busy context could advance the generation
counter enough to evict images from another context even when the
shared cache was still comfortably under its entry and byte limits.

Track recency from actual image-cache hits and inserts instead, and
evict the least-recently used entries only when the existing global
entry or byte caps are exceeded. This keeps cross-context reuse while
making eviction respond to memory pressure rather than unrelated
presentation churn.

Prune the decoded-image cache before handing the surface to the shared
SkiaBackendContext flush cleanup, so any SkImage references released
by the cache are visible to Skia's resource cleanup in the same flush
path.
This commit is contained in:
Aliaksandr Kalenik 2026-05-27 23:10:41 +02:00 committed by Alexander Kalenik
parent 1eb541ce9e
commit d341ed5448
2 changed files with 20 additions and 22 deletions

View file

@ -19,7 +19,6 @@
namespace Gfx {
static constexpr u64 image_cache_max_unused_generations = 120;
static constexpr size_t image_cache_max_entries = 128;
static constexpr size_t image_cache_max_bytes = 64 * MiB;
@ -56,35 +55,41 @@ struct DecodedImageFrameSkiaImageCache::Impl {
struct CachedImage {
sk_sp<SkImage> image;
u64 last_used_generation { 0 };
u64 last_used_sequence_number { 0 };
size_t approximate_byte_size { 0 };
};
u64 next_use_sequence_number()
{
return ++use_sequence_number;
}
void prune_to_limits()
{
while (images.size() > image_cache_max_entries || approximate_byte_size > image_cache_max_bytes) {
Optional<DecodedImageFrame> oldest_frame;
Optional<u64> oldest_generation;
Optional<DecodedImageFrame> least_recently_used_frame;
Optional<u64> least_recently_used_sequence_number;
for (auto const& image : images) {
if (!oldest_generation.has_value() || image.value.last_used_generation < oldest_generation.value()) {
oldest_frame = image.key;
oldest_generation = image.value.last_used_generation;
if (!least_recently_used_sequence_number.has_value()
|| image.value.last_used_sequence_number < least_recently_used_sequence_number.value()) {
least_recently_used_frame = image.key;
least_recently_used_sequence_number = image.value.last_used_sequence_number;
}
}
if (!oldest_frame.has_value())
if (!least_recently_used_frame.has_value())
break;
auto cached_image = images.get(oldest_frame.value()).release_value();
auto cached_image = images.get(least_recently_used_frame.value()).release_value();
approximate_byte_size -= min(approximate_byte_size, cached_image.approximate_byte_size);
images.remove(oldest_frame.value());
images.remove(least_recently_used_frame.value());
}
}
RefPtr<SkiaBackendContext> skia_backend_context;
HashMap<DecodedImageFrame, CachedImage, DecodedImageFrameKeyTraits> images;
size_t approximate_byte_size { 0 };
u64 generation { 0 };
u64 use_sequence_number { 0 };
};
DecodedImageFrameSkiaImageCache::DecodedImageFrameSkiaImageCache()
@ -103,7 +108,7 @@ sk_sp<SkImage> DecodedImageFrameSkiaImageCache::image_for_frame(DecodedImageFram
{
auto const& bitmap = frame.bitmap();
if (auto it = m_impl->images.find(frame); it != m_impl->images.end()) {
it->value.last_used_generation = m_impl->generation;
it->value.last_used_sequence_number = m_impl->next_use_sequence_number();
return it->value.image;
}
@ -123,7 +128,7 @@ sk_sp<SkImage> DecodedImageFrameSkiaImageCache::image_for_frame(DecodedImageFram
Impl::CachedImage cached_image {
.image = image,
.last_used_generation = m_impl->generation,
.last_used_sequence_number = m_impl->next_use_sequence_number(),
.approximate_byte_size = bitmap.size_in_bytes(),
};
m_impl->approximate_byte_size += cached_image.approximate_byte_size;
@ -134,14 +139,7 @@ sk_sp<SkImage> DecodedImageFrameSkiaImageCache::image_for_frame(DecodedImageFram
void DecodedImageFrameSkiaImageCache::prune()
{
m_impl->images.remove_all_matching([this](auto const&, auto const& cached_image) {
if (m_impl->generation - cached_image.last_used_generation <= image_cache_max_unused_generations)
return false;
m_impl->approximate_byte_size -= min(m_impl->approximate_byte_size, cached_image.approximate_byte_size);
return true;
});
m_impl->prune_to_limits();
++m_impl->generation;
}
}

View file

@ -106,20 +106,20 @@ static SkM44 to_skia_matrix4x4(Gfx::FloatMatrix4x4 const& matrix)
void DisplayListPlayerSkia::flush(Gfx::PaintingSurface& surface)
{
m_image_cache.prune();
if (auto context = surface.skia_backend_context())
context->flush_and_submit(&surface.sk_surface());
surface.flush();
m_image_cache.prune();
}
void DisplayListPlayerSkia::flush_async(Gfx::PaintingSurface& surface, Function<void()>&& callback)
{
m_image_cache.prune();
if (auto context = surface.skia_backend_context())
context->flush_and_submit_async(&surface.sk_surface(), move(callback));
else
callback();
surface.flush();
m_image_cache.prune();
}
static void paint_scrollbar_into_surface(Gfx::PaintingSurface& surface, PaintScrollBar const& command)