Chromium Code Reviews| Index: third_party/WebKit/Source/platform/graphics/BitmapImage.cpp |
| diff --git a/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp |
| index fad71475e10d2082229bafd35d958a839138e19c..be7d007001675171d26b91a9861a95177b6b0d0e 100644 |
| --- a/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp |
| +++ b/third_party/WebKit/Source/platform/graphics/BitmapImage.cpp |
| @@ -26,6 +26,7 @@ |
| #include "platform/graphics/BitmapImage.h" |
| +#include "base/containers/mru_cache.h" |
| #include "platform/PlatformInstrumentation.h" |
| #include "platform/Timer.h" |
| #include "platform/TraceEvent.h" |
| @@ -36,9 +37,81 @@ |
| #include "platform/graphics/StaticBitmapImage.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| +#include "wtf/CheckedNumeric.h" |
| #include "wtf/PassRefPtr.h" |
| #include "wtf/text/WTFString.h" |
| +namespace { |
| + |
| +const size_t kMaxAnimationCacheSize = 1024 * 1024 * 96; |
| +const size_t kMaxCacheSizeForAnimation = 1024 * 1024 * 24; |
|
Peter Kasting
2016/04/28 23:07:54
These limits seem not only arbitrary but rather al
aleksandar.stojiljkovic
2016/04/29 17:17:43
Done.
Second patch removes this code and also redu
|
| + |
| +using ImageMRUCache = base::HashingMRUCache<blink::BitmapImage*, size_t>; |
| + |
| +class AnimatedBitmapImageCache : public ImageMRUCache { |
| +public: |
| + static AnimatedBitmapImageCache& AnimatedBitmapImageCache::instance() |
| + { |
| + DEFINE_THREAD_SAFE_STATIC_LOCAL(AnimatedBitmapImageCache, cache, new AnimatedBitmapImageCache); |
| + return cache; |
| + } |
| + |
| + void EraseBitmap(blink::BitmapImage* pBitmapImage) |
|
Peter Kasting
2016/04/28 23:07:54
Nit: Use Google style for param naming, no Hungari
aleksandar.stojiljkovic
2016/04/29 17:17:43
Done.
removed the code in second patch.
|
| + { |
| + auto image_it = Peek(pBitmapImage); |
| + if (image_it != end()) { |
| + m_cacheSize -= image_it->second; |
| + Erase(image_it); |
| + } |
| + } |
| + |
| + void makeSpaceInCache(size_t bytesAboutToAdd) |
| + { |
| + size_t maxSize = m_maxCacheSize - std::min(bytesAboutToAdd, m_maxCacheSize); |
| + if (m_cacheSize <= maxSize) |
| + return; |
| + for (auto it = rbegin(); m_cacheSize > maxSize && it != rend(); it = rbegin()) |
| + it->first->destroyDecodedData(); |
| + } |
| + |
| + void touch(blink::BitmapImage* pBitmapImage) |
| + { |
| + // Mark that bitmap image was used in MRU cache. |
| + Get(pBitmapImage); |
| + } |
| + |
| + void cachedSizeChanged(blink::BitmapImage* pBitmapImage, int delta) |
| + { |
| + auto image_it = Peek(pBitmapImage); |
| + if (image_it != end()) { |
| + base::CheckedNumeric<intptr_t> signedFramesSize(image_it->second); |
|
Peter Kasting
2016/04/28 23:07:54
intptr_t is not an appropriate type to use for a s
aleksandar.stojiljkovic
2016/04/29 17:17:43
Done.
Removed the code in second patch.
Needs to b
|
| + signedFramesSize += delta; |
| + image_it->second = safeCast<size_t>(signedFramesSize.ValueOrDie()); |
| + } |
| + base::CheckedNumeric<intptr_t> signedCacheSize(m_cacheSize); |
| + signedCacheSize += delta; |
| + m_cacheSize = safeCast<size_t>(signedCacheSize.ValueOrDie()); |
| + } |
| + |
| + size_t m_cacheSize = 0; |
| + size_t m_maxCacheSize = kMaxAnimationCacheSize; |
| + size_t m_maxCacheSizeForAnimation = kMaxCacheSizeForAnimation; |
| + |
| +private: |
| + AnimatedBitmapImageCache() |
| + : ImageMRUCache(ImageMRUCache::NO_AUTO_EVICT) |
| + { |
| + } |
| + |
| + ~AnimatedBitmapImageCache() override |
| + { |
| + } |
| + |
| + DISALLOW_COPY_AND_ASSIGN(AnimatedBitmapImageCache); |
| +}; |
| + |
| +} |
| + |
| namespace blink { |
| PassRefPtr<BitmapImage> BitmapImage::createWithOrientationForTesting(const SkBitmap& bitmap, ImageOrientation orientation) |
| @@ -57,6 +130,7 @@ PassRefPtr<BitmapImage> BitmapImage::createWithOrientationForTesting(const SkBit |
| BitmapImage::BitmapImage(ImageObserver* observer) |
| : Image(observer) |
| , m_currentFrame(0) |
| + , m_disposeLaterFrame(kNotFound) |
| , m_repetitionCount(cAnimationNone) |
| , m_repetitionCountStatus(Unknown) |
| , m_repetitionsComplete(0) |
| @@ -76,6 +150,7 @@ BitmapImage::BitmapImage(const SkBitmap& bitmap, ImageObserver* observer) |
| : Image(observer) |
| , m_size(bitmap.width(), bitmap.height()) |
| , m_currentFrame(0) |
| + , m_disposeLaterFrame(kNotFound) |
| , m_repetitionCount(cAnimationNone) |
| , m_repetitionCountStatus(Unknown) |
| , m_repetitionsComplete(0) |
| @@ -100,6 +175,7 @@ BitmapImage::BitmapImage(const SkBitmap& bitmap, ImageObserver* observer) |
| BitmapImage::~BitmapImage() |
| { |
| stopAnimation(); |
| + AnimatedBitmapImageCache::instance().EraseBitmap(this); |
| } |
| bool BitmapImage::currentFrameHasSingleSecurityOrigin() const |
| @@ -107,7 +183,7 @@ bool BitmapImage::currentFrameHasSingleSecurityOrigin() const |
| return true; |
| } |
| -void BitmapImage::destroyDecodedData(bool destroyAll) |
| +void BitmapImage::destroyDecodedData() |
| { |
| for (size_t i = 0; i < m_frames.size(); ++i) { |
| // The underlying frame isn't actually changing (we're just trying to |
| @@ -115,25 +191,12 @@ void BitmapImage::destroyDecodedData(bool destroyAll) |
| // the metadata. |
| m_frames[i].clear(false); |
| } |
| + AnimatedBitmapImageCache::instance().EraseBitmap(this); |
| - size_t frameBytesCleared = m_source.clearCacheExceptFrame(destroyAll ? kNotFound : m_currentFrame); |
| + size_t frameBytesCleared = m_source.clearCacheExceptFrame(kNotFound); |
| notifyMemoryChanged(-safeCast<int>(frameBytesCleared)); |
| } |
| -void BitmapImage::destroyDecodedDataIfNecessary() |
| -{ |
| - // Animated images >5MB are considered large enough that we'll only hang on |
| - // to one frame at a time. |
| - static const size_t cLargeAnimationCutoff = 5242880; |
| - size_t allFrameBytes = 0; |
| - for (size_t i = 0; i < m_frames.size(); ++i) |
| - allFrameBytes += m_frames[i].m_frameBytes; |
| - |
| - if (allFrameBytes > cLargeAnimationCutoff) { |
| - destroyDecodedData(false); |
| - } |
| -} |
| - |
| void BitmapImage::notifyMemoryChanged(int delta) |
| { |
| if (delta && getImageObserver()) |
| @@ -149,7 +212,7 @@ int BitmapImage::totalFrameBytes() |
| return safeCast<int>(totalBytes); |
| } |
| -void BitmapImage::cacheFrame(size_t index) |
| +PassRefPtr<SkImage> BitmapImage::cacheFrame(size_t index) |
| { |
| size_t numFrames = frameCount(); |
| if (m_frames.size() < numFrames) |
| @@ -159,7 +222,19 @@ void BitmapImage::cacheFrame(size_t index) |
| // We are caching frame snapshots. This is OK even for partially decoded frames, |
| // as they are cleared by dataChanged() when new data arrives. |
| - m_frames[index].m_frame = m_source.createFrameAtIndex(index); |
| + RefPtr<SkImage> image = m_source.createFrameAtIndex(index); |
| + size_t frameBytes = m_source.frameBytesAtIndex(index); |
| + |
| + if (frameCount() > 1) { |
| + // Making space in cache might change and notify on memory change and |
| + // it is needed to notify on any change before and after the call. |
|
Peter Kasting
2016/04/28 23:07:54
Sorry, I don't understand what this comment is say
|
| + notifyMemoryChanged(totalFrameBytes() - deltaBytes); |
| + AnimatedBitmapImageCache::instance().makeSpaceInCache(frameBytes); |
| + deltaBytes = totalFrameBytes(); |
| + } |
| + |
| + m_frames[index].m_frame = image; |
| + m_frames[index].m_frameBytes = frameBytes; |
| m_frames[index].m_orientation = m_source.orientationAtIndex(index); |
| m_frames[index].m_haveMetadata = true; |
| @@ -167,7 +242,9 @@ void BitmapImage::cacheFrame(size_t index) |
| if (repetitionCount(false) != cAnimationNone) |
| m_frames[index].m_duration = m_source.frameDurationAtIndex(index); |
| m_frames[index].m_hasAlpha = m_source.frameHasAlphaAtIndex(index); |
| - m_frames[index].m_frameBytes = m_source.frameBytesAtIndex(index); |
| + |
| + if (frameCount() > 1) |
| + onAnimationFrameCached(index); |
| const IntSize frameSize(index ? m_source.frameSizeAtIndex(index) : m_size); |
| if (frameSize != m_size) |
| @@ -178,6 +255,7 @@ void BitmapImage::cacheFrame(size_t index) |
| // decoding multiple frames to decode the current frame. |
| deltaBytes = totalFrameBytes() - deltaBytes; |
| notifyMemoryChanged(deltaBytes); |
| + return image.release(); |
| } |
| void BitmapImage::updateSize() const |
| @@ -232,8 +310,11 @@ bool BitmapImage::dataChanged(bool allDataReceived) |
| // NOTE: Don't call frameIsCompleteAtIndex() here, that will try to |
| // decode any uncached (i.e. never-decoded or |
| // cleared-on-a-previous-pass) frames! |
| - if (m_frames[i].m_haveMetadata && !m_frames[i].m_isComplete) |
| + if (m_frames[i].m_haveMetadata && !m_frames[i].m_isComplete) { |
| + if (m_frames.size() > 1) |
| + AnimatedBitmapImageCache::instance().cachedSizeChanged(this, -static_cast<int>(m_frames[i].m_frameBytes)); |
| m_frames[i].clear(true); |
| + } |
| } |
| // Feed all the data we've seen so far to the image decoder. |
| @@ -297,7 +378,7 @@ void BitmapImage::draw(SkCanvas* canvas, const SkPaint& paint, const FloatRect& |
| WebCoreClampingModeToSkiaRectConstraint(clampMode)); |
| canvas->restoreToCount(initialSaveCount); |
| - if (currentFrameIsLazyDecoded()) |
| + if (image->isLazyGenerated()) |
| PlatformInstrumentation::didDrawLazyPixelRef(image->uniqueID()); |
| if (ImageObserver* observer = getImageObserver()) |
| @@ -339,23 +420,30 @@ bool BitmapImage::isSizeAvailable() |
| return m_sizeAvailable; |
| } |
| -bool BitmapImage::ensureFrameIsCached(size_t index) |
| +PassRefPtr<SkImage> BitmapImage::frameAtIndex(size_t index) |
| { |
| if (index >= frameCount()) |
| - return false; |
| + return nullptr; |
| - if (index >= m_frames.size() || !m_frames[index].m_frame) |
| - cacheFrame(index); |
| + // Limit cache size; remove previously cached frame from this bitmap that |
| + // shouldn't be cached anymore. |
| + if (index != m_disposeLaterFrame && m_disposeLaterFrame != kNotFound |
| + && m_frames[m_disposeLaterFrame].m_frame && !shouldCacheFrame(m_disposeLaterFrame)) { |
| + AnimatedBitmapImageCache::instance().cachedSizeChanged(this, -static_cast<int>(m_frames[m_disposeLaterFrame].m_frameBytes)); |
| + m_frames[m_disposeLaterFrame].clear(false); |
| + m_disposeLaterFrame = kNotFound; |
| + } |
| - return true; |
| -} |
| + RefPtr<SkImage> image; |
| + if (index < m_frames.size()) |
| + image = m_frames[index].m_frame; |
| -PassRefPtr<SkImage> BitmapImage::frameAtIndex(size_t index) |
| -{ |
| - if (!ensureFrameIsCached(index)) |
| - return nullptr; |
| - |
| - return m_frames[index].m_frame; |
| + if (image) { |
| + if (m_frames.size() > 1) |
| + AnimatedBitmapImageCache::instance().touch(this); |
| + return image.release(); |
| + } |
| + return cacheFrame(index); |
| } |
| bool BitmapImage::frameIsCompleteAtIndex(size_t index) |
| @@ -571,9 +659,6 @@ void BitmapImage::resetAnimation() |
| m_repetitionsComplete = 0; |
| m_desiredFrameStartTime = 0; |
| m_animationFinished = false; |
| - |
| - // For extremely large animations, when the animation is reset, we just throw everything away. |
| - destroyDecodedDataIfNecessary(); |
| } |
| bool BitmapImage::maybeAnimated() |
| @@ -631,13 +716,57 @@ bool BitmapImage::internalAdvanceAnimation(bool skippingFrames) |
| } else |
| m_currentFrame = 0; |
| } |
| - destroyDecodedDataIfNecessary(); |
| // We need to draw this frame if we advanced to it while not skipping, or if |
| // while trying to skip frames we hit the last frame and thus had to stop. |
| if (skippingFrames != advancedAnimation) |
| getImageObserver()->animationAdvanced(this); |
| + |
| return advancedAnimation; |
| } |
| +bool BitmapImage::shouldCacheFrame(size_t index) |
| +{ |
| + if (m_frames.size() <= 1) |
| + return true; |
| + // For large animations it doesnt make sense to cache the most recently used |
| + // frames since when looping this would lead to continuous evict, re-decode, |
| + // re-cache for every frame rendered. Instead, cache only first |
| + // (cache->m_maxCacheSizeForAnimation / frameSize) frames - we can prevent |
| + // re-decode and re-caching for them. |
|
Peter Kasting
2016/04/28 23:07:54
This doesn't seem like a good policy.
If you want
|
| + // Asuming all frames are of the same size and 32bit per pixel is good |
| + // enough for purpose here - controlling cache size. |
| + assert(isSizeAvailable()); |
| + return ((index + 1) * sizeof(ImageFrame::PixelData) * size().width() * size().height() |
| + <= AnimatedBitmapImageCache::instance().m_maxCacheSizeForAnimation); |
| +} |
| + |
| +void BitmapImage::onAnimationFrameCached(size_t index) |
| +{ |
| + // calculate cached frame bytes for all frames. |
| + size_t cachedFrameBytes = 0; |
| + for (size_t i = 0; i < m_frames.size(); ++i) |
| + cachedFrameBytes += m_frames[i].m_frameBytes; |
| + |
| + AnimatedBitmapImageCache* cache = &AnimatedBitmapImageCache::instance(); |
| + bool shouldCache = shouldCacheFrame(index); |
| + // If the frame shouldn't be cached, don't change order in MRU cache. |
| + auto image_it = shouldCache ? cache->Get(this) : cache->Peek(this); |
| + if (image_it != cache->end()) { |
| + // image_it->second - frame bytes of cached BitmapImage frames. |
| + cache->m_cacheSize -= image_it->second; |
| + image_it->second = cachedFrameBytes; |
| + } else { |
| + cache->Put(this, cachedFrameBytes); |
| + } |
| + cache->m_cacheSize += cachedFrameBytes; |
| + m_disposeLaterFrame = shouldCache ? kNotFound : index; |
| +} |
| + |
| +void BitmapImage::setAnimationCacheSizeForTesting(size_t maxCacheSize, size_t maxAnimationSizeInCache) |
| +{ |
| + AnimatedBitmapImageCache::instance().m_maxCacheSize = maxCacheSize; |
| + AnimatedBitmapImageCache::instance().m_maxCacheSizeForAnimation = maxAnimationSizeInCache; |
| +} |
| + |
| } // namespace blink |