author | Justin Lebar <justin.lebar@gmail.com> |
Fri, 09 Mar 2012 17:21:01 -0500 | |
changeset 91935 | f7890083256286e88623561df39ac3903a5a48ac |
parent 91934 | 45d2f5e2fe317cff073fdc206cfa5b154501c785 |
child 91936 | 6184b50776fc79cf5b76c60734cdf646ceee745f |
push id | 136 |
push user | lsblakk@mozilla.com |
push date | Fri, 01 Jun 2012 02:39:32 +0000 |
treeherder | mozilla-release@7ebf7352c959 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | joe |
bugs | 732820 |
milestone | 13.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/image/src/DiscardTracker.cpp +++ b/image/src/DiscardTracker.cpp @@ -1,245 +1,198 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bobby Holley <bobbyholley@gmail.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsComponentManagerUtils.h" #include "nsITimer.h" #include "RasterImage.h" #include "DiscardTracker.h" #include "mozilla/Preferences.h" namespace mozilla { namespace image { -static bool sInitialized = false; -static bool sTimerOn = false; -static PRUint32 sMinDiscardTimeoutMs = 10000; /* Default if pref unreadable. */ -static nsITimer *sTimer = nsnull; -static struct DiscardTrackerNode sHead, sSentinel, sTail; +static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms"; + +/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages; +/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer; +/* static */ bool DiscardTracker::sInitialized = false; +/* static */ bool DiscardTracker::sTimerOn = false; +/* static */ bool DiscardTracker::sDiscardRunnablePending = false; +/* static */ ssize_t DiscardTracker::sCurrentDecodedImageBytes = 0; +/* static */ PRUint32 DiscardTracker::sMinDiscardTimeoutMs = 10000; +/* static */ PRUint32 DiscardTracker::sMaxDecodedImageKB = 42 * 1024; /* - * Puts an image in the back of the tracker queue. If the image is already - * in the tracker, this removes it first. + * When we notice we're using too much memory for decoded images, we enqueue a + * DiscardRunnable, which runs this code. */ -nsresult -DiscardTracker::Reset(DiscardTrackerNode *node) +NS_IMETHODIMP +DiscardTracker::DiscardRunnable::Run() { - nsresult rv; -#ifdef DEBUG - bool isSentinel = (node == &sSentinel); + sDiscardRunnablePending = false; + DiscardTracker::DiscardNow(); + return NS_OK; +} - // Sanity check the node. - NS_ABORT_IF_FALSE(isSentinel || node->curr, "Node doesn't point to anything!"); - - // We should not call this function if we can't discard - NS_ABORT_IF_FALSE(isSentinel || node->curr->CanDiscard(), - "trying to reset discarding but can't discard!"); +int +DiscardTimeoutChangedCallback(const char* aPref, void *aClosure) +{ + DiscardTracker::ReloadTimeout(); + return 0; +} - // As soon as an image becomes animated it is set non-discardable - NS_ABORT_IF_FALSE(isSentinel || !node->curr->mAnim, - "Trying to reset discarding on animated image!"); -#endif +nsresult +DiscardTracker::Reset(Node *node) +{ + // We shouldn't call Reset() with a null |img| pointer, on images which can't + // be discarded, or on animated images (which should be marked as + // non-discardable, anyway). + MOZ_ASSERT(node->img); + MOZ_ASSERT(node->img->CanDiscard()); + MOZ_ASSERT(!node->img->mAnim); - // Initialize the first time through + // Initialize the first time through. + nsresult rv; if (NS_UNLIKELY(!sInitialized)) { rv = Initialize(); NS_ENSURE_SUCCESS(rv, rv); } - // Remove the node if it's in the list. - Remove(node); + // Insert the node at the front of the list and note when it was inserted. + bool wasInList = node->isInList(); + if (wasInList) { + node->remove(); + } + node->timestamp = TimeStamp::Now(); + sDiscardableImages.insertFront(node); - // Append it to the list. - node->prev = sTail.prev; - node->next = &sTail; - node->prev->next = sTail.prev = node; + // If the node wasn't already in the list of discardable images, then we may + // need to discard some images to stay under the sMaxDecodedImageKB limit. + // Call MaybeDiscardSoon to do this check. + if (!wasInList) { + MaybeDiscardSoon(); + } - // Make sure the timer is running - rv = TimerOn(); + // Make sure the timer is running. + rv = EnableTimer(); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } -/* - * Removes a node from the tracker. No-op if the node is currently untracked. +void +DiscardTracker::Remove(Node *node) +{ + if (node->isInList()) + node->remove(); + + if (sDiscardableImages.isEmpty()) + DisableTimer(); +} + +/** + * Shut down the tracker, deallocating the timer. */ void -DiscardTracker::Remove(DiscardTrackerNode *node) +DiscardTracker::Shutdown() { - NS_ABORT_IF_FALSE(node != nsnull, "Can't pass null node"); - - // If we're not in a list, we have nothing to do. - if ((node->prev == nsnull) || (node->next == nsnull)) { - NS_ABORT_IF_FALSE(node->prev == node->next, - "Node is half in a list!"); - return; + if (sTimer) { + sTimer->Cancel(); + sTimer = NULL; } - - // Connect around ourselves - node->prev->next = node->next; - node->next->prev = node->prev; - - // Clean up the node we removed. - node->prev = node->next = nsnull; } /* * Discard all the images we're tracking. */ void DiscardTracker::DiscardAll() { if (!sInitialized) return; - // Remove the sentinel from the list so that the only elements in the list - // which don't track an image are the head and tail. - Remove(&sSentinel); + sDiscardableImages.clear(); - // Discard all tracked images. - for (DiscardTrackerNode *node = sHead.next; - node != &sTail; node = sHead.next) { - NS_ABORT_IF_FALSE(node->curr, "empty node!"); - Remove(node); - node->curr->Discard(); - } - - // Add the sentinel back to the (now empty) list. - Reset(&sSentinel); - - // Because the sentinel is the only element in the list, the next timer event - // would be a no-op. Disable the timer as an optimization. - TimerOff(); + // The list is empty, so there's no need to leave the timer on. + DisableTimer(); } -static int -DiscardTimeoutChangedCallback(const char* aPref, void *aClosure) +void +DiscardTracker::InformAllocation(ssize_t bytes) { - DiscardTracker::ReloadTimeout(); - return 0; + // This function is called back e.g. from RasterImage::Discard(); be careful! + + sCurrentDecodedImageBytes += bytes; + MOZ_ASSERT(sCurrentDecodedImageBytes >= 0); + + // If we're using too much memory for decoded images, MaybeDiscardSoon will + // enqueue a callback to discard some images. + MaybeDiscardSoon(); } /** * Initialize the tracker. */ nsresult DiscardTracker::Initialize() { - nsresult rv; - - // Set up the list. Head<->Sentinel<->Tail - sHead.curr = sTail.curr = sSentinel.curr = nsnull; - sHead.prev = sTail.next = nsnull; - sHead.next = sTail.prev = &sSentinel; - sSentinel.prev = &sHead; - sSentinel.next = &sTail; - // Watch the timeout pref for changes. Preferences::RegisterCallback(DiscardTimeoutChangedCallback, - DISCARD_TIMEOUT_PREF); + sDiscardTimeoutPref); - ReloadTimeout(); + Preferences::AddUintVarCache(&sMaxDecodedImageKB, + "image.mem.max_decoded_image_kb", + 50 * 1024); - // Create and start the timer - nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1"); - NS_ENSURE_TRUE(t, NS_ERROR_OUT_OF_MEMORY); - t.forget(&sTimer); - rv = TimerOn(); - NS_ENSURE_SUCCESS(rv, rv); + // Create the timer. + sTimer = do_CreateInstance("@mozilla.org/timer;1"); + + // Read the timeout pref and start the timer. + ReloadTimeout(); // Mark us as initialized sInitialized = true; return NS_OK; } /** - * Shut down the tracker, deallocating the timer. - */ -void -DiscardTracker::Shutdown() -{ - if (sTimer) { - sTimer->Cancel(); - NS_RELEASE(sTimer); - sTimer = nsnull; - } -} - -/** * Read the discard timeout from about:config. */ void DiscardTracker::ReloadTimeout() { - nsresult rv; - - // read the timeout pref + // Read the timeout pref. PRInt32 discardTimeout; - rv = Preferences::GetInt(DISCARD_TIMEOUT_PREF, &discardTimeout); + nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout); - // If we got something bogus, return + // If we got something bogus, return. if (!NS_SUCCEEDED(rv) || discardTimeout <= 0) return; - // If the value didn't change, return + // If the value didn't change, return. if ((PRUint32) discardTimeout == sMinDiscardTimeoutMs) return; - // Update the value + // Update the value. sMinDiscardTimeoutMs = (PRUint32) discardTimeout; - // If the timer's on, restart the clock to make changes take effect - if (sTimerOn) { - TimerOff(); - TimerOn(); - } + // Restart the timer so the new timeout takes effect. + DisableTimer(); + EnableTimer(); } /** * Enables the timer. No-op if the timer is already running. */ nsresult -DiscardTracker::TimerOn() +DiscardTracker::EnableTimer() { // Nothing to do if the timer's already on. if (sTimerOn) return NS_OK; sTimerOn = true; // Activate return sTimer->InitWithFuncCallback(TimerCallback, @@ -247,47 +200,77 @@ DiscardTracker::TimerOn() sMinDiscardTimeoutMs, nsITimer::TYPE_REPEATING_SLACK); } /* * Disables the timer. No-op if the timer isn't running. */ void -DiscardTracker::TimerOff() +DiscardTracker::DisableTimer() { // Nothing to do if the timer's already off. if (!sTimerOn) return; sTimerOn = false; // Deactivate sTimer->Cancel(); } /** - * Routine activated when the timer fires. This discards everything - * in front of sentinel, and resets the sentinel to the back of the - * list. + * Routine activated when the timer fires. This discards everything that's + * older than sMinDiscardTimeoutMs, and tries to discard enough images so that + * we go under sMaxDecodedImageKB. */ void DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure) { - DiscardTrackerNode *node; + DiscardNow(); +} + +void +DiscardTracker::DiscardNow() +{ + // Assuming the list is ordered with oldest discard tracker nodes at the back + // and newest ones at the front, iterate from back to front discarding nodes + // until we encounter one which is new enough to keep and until we go under + // our sMaxDecodedImageKB limit. - // Remove and discard everything before the sentinel - for (node = sSentinel.prev; node != &sHead; node = sSentinel.prev) { - NS_ABORT_IF_FALSE(node->curr, "empty node!"); - Remove(node); - node->curr->Discard(); + TimeStamp now = TimeStamp::Now(); + Node* node; + while ((node = sDiscardableImages.getLast())) { + if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs || + sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) { + + // Discarding the image should cause sCurrentDecodedImageBytes to + // decrease via a call to InformAllocation(). + node->img->Discard(); + + // Careful: Discarding may have caused the node to have been removed + // from the list. + Remove(node); + } + else { + break; + } } - // Append the sentinel to the back of the list - Reset(&sSentinel); + // If the list is empty, disable the timer. + if (sDiscardableImages.isEmpty()) + DisableTimer(); +} - // If there's nothing in front of the sentinel, the next callback - // is guaranteed to be a no-op. Disable the timer as an optimization. - if (sSentinel.prev == &sHead) - TimerOff(); +void +DiscardTracker::MaybeDiscardSoon() +{ + // Are we carrying around too much decoded image data? If so, enqueue an + // event to try to get us down under our limit. + if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 && + !sDiscardableImages.isEmpty() && !sDiscardRunnablePending) { + sDiscardRunnablePending = true; + nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable(); + NS_DispatchToCurrentThread(runnable); + } } } // namespace image } // namespace mozilla
--- a/image/src/DiscardTracker.h +++ b/image/src/DiscardTracker.h @@ -1,90 +1,116 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Bobby Holley <bobbyholley@gmail.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_imagelib_DiscardTracker_h_ #define mozilla_imagelib_DiscardTracker_h_ -#define DISCARD_TIMEOUT_PREF "image.mem.min_discard_timeout_ms" +#include "mozilla/LinkedList.h" +#include "mozilla/TimeStamp.h" class nsITimer; namespace mozilla { namespace image { + class RasterImage; -// Struct to make a RasterImage insertable into the tracker list. This -// is embedded within each RasterImage object, and we do 'this->curr = this' -// on RasterImage construction. Thus, a RasterImage must always call -// DiscardTracker::Remove() in its destructor to avoid having the tracker -// point to bogus memory. -struct DiscardTrackerNode -{ - // Pointer to the RasterImage that this node tracks - RasterImage *curr; - - // Pointers to the previous and next nodes in the list - DiscardTrackerNode *prev, *next; -}; - /** - * This static class maintains a linked list of RasterImage nodes. When Reset() - * is called, the node is removed from its position in the list (if it was there - * before) and appended to the end. When Remove() is called, the node is removed - * from the list. The timer fires once every MIN_DISCARD_TIMEOUT_MS ms. When it - * does, it calls Discard() on each container preceding it, and then appends - * itself to the end of the list. Thus, the discard timeout varies between - * MIN_DISCARD_TIMEOUT_MS and 2*MIN_DISCARD_TIMEOUT_MS. + * This static class maintains a linked list of RasterImage objects which are + * eligible for discarding. + * + * When Reset() is called, the node is removed from its position in the list + * (if it was there before) and appended to the beginnings of the list. + * + * Periodically (on a timer and when we notice that we're using more memory + * than we'd like for decoded images), we go through the list and discard + * decoded data from images at the end of the list. */ class DiscardTracker { public: - static nsresult Reset(struct DiscardTrackerNode *node); - static void Remove(struct DiscardTrackerNode *node); + /** + * The DiscardTracker keeps a linked list of Node objects. Each object + * points to a RasterImage and contains a timestamp indicating when the + * node was inserted into the tracker. + * + * This structure is embedded within each RasterImage object, and we do + * |mDiscardTrackerNode.img = this| on RasterImage construction. Thus, a + * RasterImage must always call DiscardTracker::Remove() in its destructor + * to avoid having the tracker point to bogus memory. + */ + struct Node : public LinkedListElement<Node> + { + RasterImage *img; + TimeStamp timestamp; + }; + + /** + * Add an image to the front of the tracker's list, or move it to the front + * if it's already in the list. + */ + static nsresult Reset(struct Node* node); + + /** + * Remove a node from the tracker; do nothing if the node is currently + * untracked. + */ + static void Remove(struct Node* node); + + /** + * Shut the discard tracker down. This should be called on XPCOM shutdown + * so we destroy the discard timer's nsITimer. + */ static void Shutdown(); - static void ReloadTimeout(); + + /** + * Discard the decoded image data for all images tracked by the discard + * tracker. + */ static void DiscardAll(); + + /** + * Inform the discard tracker that we've allocated or deallocated some + * memory for a decoded image. We use this to determine when we've + * allocated too much memory and should discard some images. + */ + static void InformAllocation(ssize_t bytes); + private: + /** + * This is called when the discard timer fires; it calls into DiscardNow(). + */ + friend int DiscardTimeoutChangedCallback(const char* aPref, void *aClosure); + + /** + * When run, this runnable sets sDiscardRunnablePending to false and calls + * DiscardNow(). + */ + class DiscardRunnable : public nsRunnable + { + NS_IMETHOD Run(); + }; + static nsresult Initialize(); - static nsresult TimerOn(); - static void TimerOff(); + static void ReloadTimeout(); + static nsresult EnableTimer(); + static void DisableTimer(); + static void MaybeDiscardSoon(); static void TimerCallback(nsITimer *aTimer, void *aClosure); + static void DiscardNow(); + + static LinkedList<Node> sDiscardableImages; + static nsCOMPtr<nsITimer> sTimer; + static bool sInitialized; + static bool sTimerOn; + static bool sDiscardRunnablePending; + static ssize_t sCurrentDecodedImageBytes; + static PRUint32 sMinDiscardTimeoutMs; + static PRUint32 sMaxDecodedImageKB; }; } // namespace image } // namespace mozilla #endif /* mozilla_imagelib_DiscardTracker_h_ */
--- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -209,18 +209,17 @@ RasterImage::RasterImage(imgStatusTracke mDiscardable(false), mHasSourceData(false), mDecoded(false), mHasBeenDecoded(false), mInDecoder(false), mAnimationFinished(false) { // Set up the discard tracker node. - mDiscardTrackerNode.curr = this; - mDiscardTrackerNode.prev = mDiscardTrackerNode.next = nsnull; + mDiscardTrackerNode.img = this; Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0); // Statistics num_containers++; // Register our pref observers if we haven't yet. if (NS_UNLIKELY(!gInitializedPrefCaches)) { InitPrefCaches(); @@ -2216,17 +2215,17 @@ RasterImage::CanForciblyDiscard() { return mDiscardable && // ...Enabled at creation time... mHasSourceData; // ...have the source data... } // Helper method to tell us whether the clock is currently running for // discarding this image. Mainly for assertions. bool RasterImage::DiscardingActive() { - return !!(mDiscardTrackerNode.prev || mDiscardTrackerNode.next); + return mDiscardTrackerNode.isInList(); } // Helper method to determine if we're storing the source data in a buffer // or just writing it directly to the decoder bool RasterImage::StoringSourceData() const { return (mDecodeOnDraw || mDiscardable); }
--- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -629,17 +629,17 @@ private: // data //! # loops remaining before animation stops (-1 no stop) PRInt32 mLoopCount; //! imgIDecoderObserver nsWeakPtr mObserver; // Discard members PRUint32 mLockCount; - DiscardTrackerNode mDiscardTrackerNode; + DiscardTracker::Node mDiscardTrackerNode; // Source data members FallibleTArray<char> mSourceData; nsCString mSourceDataMimeType; nsCString mURIString; friend class DiscardTracker;
--- a/image/src/imgFrame.cpp +++ b/image/src/imgFrame.cpp @@ -32,16 +32,17 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "imgFrame.h" +#include "DiscardTracker.h" #include <limits.h> #include "prmem.h" #include "prenv.h" #include "gfxPlatform.h" #include "gfxUtils.h" @@ -62,16 +63,18 @@ static PRUint32 gTotalDDBs = 0; static PRUint32 gTotalDDBSize = 0; // only use up a maximum of 64MB in DDBs #define kMaxDDBSize (64*1024*1024) // and don't let anything in that's bigger than 4MB #define kMaxSingleDDBSize (4*1024*1024) #endif +using namespace mozilla::image; + // Returns true if an image of aWidth x aHeight is allowed and legal. static bool AllowedImageSize(PRInt32 aWidth, PRInt32 aHeight) { // reject over-wide or over-tall images const PRInt32 k64KLimit = 0x0000FFFF; if (NS_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) { NS_WARNING("image too big"); return false; @@ -142,16 +145,17 @@ imgFrame::imgFrame() : mSinglePixel(false), mNeverUseDeviceSurface(false), mFormatChanged(false), mCompositingFailed(false) #ifdef USE_WIN_SURFACE , mIsDDBSurface(false) #endif , mLocked(false) + , mInformedDiscardTracker(false) { static bool hasCheckedOptimize = false; if (!hasCheckedOptimize) { if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) { gDisableOptimize = true; } hasCheckedOptimize = true; } @@ -161,16 +165,20 @@ imgFrame::~imgFrame() { PR_FREEIF(mPalettedImageData); #ifdef USE_WIN_SURFACE if (mIsDDBSurface) { gTotalDDBs--; gTotalDDBSize -= mSize.width * mSize.height * 4; } #endif + + if (mInformedDiscardTracker) { + DiscardTracker::InformAllocation(-4 * mSize.height * mSize.width); + } } nsresult imgFrame::Init(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, gfxASurface::gfxImageFormat aFormat, PRUint8 aPaletteDepth /* = 0 */) { // assert for properties that should be verified by decoders, warn for properties related to bad content if (!AllowedImageSize(aWidth, aHeight)) return NS_ERROR_FAILURE; @@ -222,16 +230,24 @@ nsresult imgFrame::Init(PRInt32 aX, PRIn #ifdef XP_MACOSX if (!mNeverUseDeviceSurface && !ShouldUseImageSurfaces()) { mQuartzSurface = new gfxQuartzImageSurface(mImageSurface); } #endif } + // Inform the discard tracker that we've allocated some memory, but only if + // we're not a paletted image (paletted images are not usually large and are + // used only for animated frames, which we don't discard). + if (!mPalettedImageData) { + DiscardTracker::InformAllocation(4 * mSize.width * mSize.height); + mInformedDiscardTracker = true; + } + return NS_OK; } nsresult imgFrame::Optimize() { if (gDisableOptimize) return NS_OK; @@ -266,16 +282,24 @@ nsresult imgFrame::Optimize() mImageSurface = nsnull; mOptSurface = nsnull; #ifdef USE_WIN_SURFACE mWinSurface = nsnull; #endif #ifdef XP_MACOSX mQuartzSurface = nsnull; #endif + + // We just dumped most of our allocated memory, so tell the discard + // tracker that we're not using any at all. + if (mInformedDiscardTracker) { + DiscardTracker::InformAllocation(-4 * mSize.width * mSize.height); + mInformedDiscardTracker = false; + } + return NS_OK; } } // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment } // if we're being forced to use image surfaces due to
--- a/image/src/imgFrame.h +++ b/image/src/imgFrame.h @@ -190,14 +190,17 @@ private: // data PRInt8 mBlendMethod; bool mSinglePixel; bool mNeverUseDeviceSurface; bool mFormatChanged; bool mCompositingFailed; /** Indicates if the image data is currently locked */ bool mLocked; + /** Have we called DiscardTracker::InformAllocation()? */ + bool mInformedDiscardTracker; + #ifdef XP_WIN bool mIsDDBSurface; #endif }; #endif /* imgFrame_h */
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -3328,16 +3328,20 @@ pref("image.mem.min_discard_timeout_ms", pref("image.mem.decode_bytes_at_a_time", 4096); // The longest time we can spend in an iteration of an async decode pref("image.mem.max_ms_before_yield", 5); // The maximum source data size for which we auto sync decode pref("image.mem.max_bytes_for_sync_decode", 150000); +// The maximum amount of decoded image data we'll willingly keep around (we +// might keep around more than this, but we'll try to get down to this value). +pref("image.mem.max_decoded_image_kb", 50 * 1024); + // WebGL prefs pref("webgl.force-enabled", false); pref("webgl.disabled", false); pref("webgl.shader_validator", true); pref("webgl.force_osmesa", false); pref("webgl.osmesalib", ""); pref("webgl.verbose", false); pref("webgl.prefer-native-gl", false);