Bug 962670 - Store decoded images in VolatileBuffers, r=seth,jrmuizel

This commit is contained in:
Michael Wu 2014-02-24 22:37:51 -05:00
parent 03b6a5a2b0
commit 7e9891b6d8
5 changed files with 233 additions and 39 deletions

View File

@ -26,6 +26,7 @@
#include "nsSize.h" // for nsIntSize
#include "nsTArray.h" // for nsTArray
#include "mozilla/Atomics.h"
#include "mozilla/WeakPtr.h"
#include "nsThreadUtils.h"
#include "mozilla/gfx/2D.h"
#include "nsDataHashtable.h"
@ -380,7 +381,7 @@ struct RemoteImageData {
* updates the shared state to point to the new image and the old image
* is immediately released (not true in Normal or Asynchronous modes).
*/
class ImageContainer {
class ImageContainer : public SupportsWeakPtr<ImageContainer> {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainer)
public:

View File

@ -922,8 +922,24 @@ RasterImage::GetFrame(uint32_t aWhichFrame,
nsIntRect framerect = frame->GetRect();
if (framerect.x == 0 && framerect.y == 0 &&
framerect.width == mSize.width &&
framerect.height == mSize.height)
framerect.height == mSize.height) {
frame->GetSurface(getter_AddRefs(framesurf));
if (!framesurf && !frame->IsSinglePixel()) {
// No reason to be optimized away here - the OS threw out the data
if (!(aFlags & FLAG_SYNC_DECODE))
return nullptr;
// Unconditionally call ForceDiscard() here because GetSurface can only
// return null when we can forcibly discard and redecode. There are two
// other cases where GetSurface() can return null - when it is a single
// pixel image, which we check before getting here, or when this is an
// indexed image, in which case we shouldn't be in this function at all.
// The only remaining possibility is that SetDiscardable() was called on
// this imgFrame, which implies the image can be redecoded.
ForceDiscard();
return GetFrame(aWhichFrame, aFlags);
}
}
// The image doesn't have a surface because it's been optimized away. Create
// one.
@ -947,7 +963,15 @@ RasterImage::GetCurrentImage()
}
nsRefPtr<gfxASurface> imageSurface = GetFrame(FRAME_CURRENT, FLAG_NONE);
NS_ENSURE_TRUE(imageSurface, nullptr);
if (!imageSurface) {
// The OS threw out some or all of our buffer. Start decoding again.
// GetFrame will only return null in the case that the image was
// discarded. We already checked that the image is decoded, so other
// error paths are not possible.
ForceDiscard();
RequestDecodeCore(ASYNCHRONOUS);
return nullptr;
}
if (!mImageContainer) {
mImageContainer = LayerManager::CreateImageContainer();
@ -981,6 +1005,10 @@ RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval)
mStatusTracker->OnUnlockedDraw();
}
if (!mImageContainer) {
mImageContainer = mImageContainerCache;
}
if (mImageContainer) {
*_retval = mImageContainer;
NS_ADDREF(*_retval);
@ -995,6 +1023,13 @@ RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval)
*_retval = mImageContainer;
NS_ADDREF(*_retval);
// We only need to be careful about holding on to the image when it is
// discardable by the OS.
if (CanForciblyDiscardAndRedecode()) {
mImageContainerCache = mImageContainer->asWeakPtr();
mImageContainer = nullptr;
}
return NS_OK;
}
@ -1392,6 +1427,12 @@ RasterImage::DecodingComplete()
// We don't optimize the frame for multipart images because we reuse
// the frame.
if ((GetNumFrames() == 1) && !mMultipart) {
// CanForciblyDiscard is used instead of CanForciblyDiscardAndRedecode
// because we know decoding is complete at this point and this is not
// an animation
if (DiscardingEnabled() && CanForciblyDiscard()) {
mFrameBlender.RawGetFrame(0)->SetDiscardable();
}
rv = mFrameBlender.RawGetFrame(0)->Optimize();
NS_ENSURE_SUCCESS(rv, rv);
}
@ -2093,13 +2134,6 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
// Figure out what kind of decode we were doing before we get rid of our decoder
bool wasSizeDecode = mDecoder->IsSizeDecode();
// Unlock the last frame (if we have any). Our invariant is that, while we
// have a decoder open, the last frame is always locked.
if (GetNumFrames() > 0) {
imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
curframe->UnlockImageData();
}
// Finalize the decoder
// null out mDecoder, _then_ check for errors on the close (otherwise the
// error routine might re-invoke ShutdownDecoder)
@ -2112,6 +2146,13 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
mInDecoder = false;
mFinishing = false;
// Unlock the last frame (if we have any). Our invariant is that, while we
// have a decoder open, the last frame is always locked.
if (GetNumFrames() > 0) {
imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
curframe->UnlockImageData();
}
// Kill off our decode request, if it's pending. (If not, this call is
// harmless.)
DecodePool::StopDecoding(this);
@ -2677,6 +2718,17 @@ RasterImage::Draw(gfxContext *aContext,
return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
}
nsRefPtr<gfxASurface> surf;
if (!frame->IsSinglePixel()) {
frame->GetSurface(getter_AddRefs(surf));
if (!surf) {
// The OS threw out some or all of our buffer. Start decoding again.
ForceDiscard();
WantDecodedFrames();
return NS_OK;
}
}
DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, aUserSpaceToImageSpace, aFill, aSubimage, aFlags);
if (mDecoded && !mDrawStartTime.IsNull()) {

View File

@ -659,6 +659,9 @@ private: // data
// Cached value for GetImageContainer.
nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
// If not cached in mImageContainer, this might have our image container
WeakPtr<mozilla::layers::ImageContainer> mImageContainerCache;
#ifdef DEBUG
uint32_t mFramesNotified;
#endif

View File

@ -12,6 +12,7 @@
#include "gfx2DGlue.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "gfxAlphaRecovery.h"
static bool gDisableOptimize = false;
@ -35,6 +36,48 @@ using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::image;
static cairo_user_data_key_t kVolatileBuffer;
static void
VolatileBufferRelease(void *vbuf)
{
delete static_cast<VolatileBufferPtr<unsigned char>*>(vbuf);
}
gfxImageSurface *
LockedImageSurface::CreateSurface(VolatileBuffer *vbuf,
const gfxIntSize& size,
gfxImageFormat format)
{
VolatileBufferPtr<unsigned char> *vbufptr =
new VolatileBufferPtr<unsigned char>(vbuf);
MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!");
long stride = gfxImageSurface::ComputeStride(size, format);
gfxImageSurface *img = new gfxImageSurface(*vbufptr, size, stride, format);
if (!img || img->CairoStatus()) {
delete img;
delete vbufptr;
return nullptr;
}
img->SetData(&kVolatileBuffer, vbufptr, VolatileBufferRelease);
return img;
}
TemporaryRef<VolatileBuffer>
LockedImageSurface::AllocateBuffer(const gfxIntSize& size,
gfxImageFormat format)
{
long stride = gfxImageSurface::ComputeStride(size, format);
RefPtr<VolatileBuffer> buf = new VolatileBuffer();
if (buf->Init(stride * size.height,
1 << gfxAlphaRecovery::GoodAlignmentLog2()))
return buf;
return nullptr;
}
// Returns true if an image of aWidth x aHeight is allowed and legal.
static bool AllowedImageSize(int32_t aWidth, int32_t aHeight)
{
@ -108,6 +151,7 @@ imgFrame::imgFrame() :
mFormatChanged(false),
mCompositingFailed(false),
mNonPremult(false),
mDiscardable(false),
mInformedDiscardTracker(false),
mDirty(false)
{
@ -174,12 +218,17 @@ nsresult imgFrame::Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
}
#endif
// For other platforms we create the image surface first and then
// possibly wrap it in a device surface. This branch is also used
// on Windows if we're not using device surfaces or if we couldn't
// create one.
if (!mImageSurface)
mImageSurface = new gfxImageSurface(gfxIntSize(mSize.width, mSize.height), mFormat);
// For other platforms, space for the image surface is first allocated in
// a volatile buffer and then wrapped by a LockedImageSurface.
// This branch is also used on Windows if we're not using device surfaces
// or if we couldn't create one.
if (!mImageSurface) {
mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
if (!mVBuf) {
return NS_ERROR_OUT_OF_MEMORY;
}
mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
}
if (!mImageSurface || mImageSurface->CairoStatus()) {
mImageSurface = nullptr;
@ -251,6 +300,7 @@ nsresult imgFrame::Optimize()
mSinglePixel = true;
// blow away the older surfaces (if they exist), to release their memory
mVBuf = nullptr;
mImageSurface = nullptr;
mOptSurface = nullptr;
#ifdef USE_WIN_SURFACE
@ -300,6 +350,7 @@ nsresult imgFrame::Optimize()
mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat);
if (mOptSurface) {
mVBuf = nullptr;
mImageSurface = nullptr;
#ifdef USE_WIN_SURFACE
mWinSurface = nullptr;
@ -483,10 +534,13 @@ uint32_t imgFrame::GetImageBytesPerRow() const
if (mImageSurface)
return mImageSurface->Stride();
if (mVBuf)
return gfxImageSurface::ComputeStride(mSize, mFormat);
if (mPaletteDepth)
return mSize.width;
NS_ERROR("GetImageBytesPerRow called with mImageSurface == null and mPaletteDepth == 0");
NS_ERROR("GetImageBytesPerRow called with mImageSurface == null, mVBuf == null and mPaletteDepth == 0");
return 0;
}
@ -569,28 +623,42 @@ nsresult imgFrame::LockImageData()
if (mPalettedImageData)
return NS_OK;
if ((mOptSurface || mSinglePixel) && !mImageSurface) {
// Recover the pixels
mImageSurface = new gfxImageSurface(gfxIntSize(mSize.width, mSize.height),
gfxImageFormat::ARGB32);
if (!mImageSurface || mImageSurface->CairoStatus())
return NS_ERROR_OUT_OF_MEMORY;
if (!mImageSurface) {
if (mVBuf) {
VolatileBufferPtr<uint8_t> ref(mVBuf);
if (ref.WasBufferPurged())
return NS_ERROR_FAILURE;
gfxContext context(mImageSurface);
context.SetOperator(gfxContext::OPERATOR_SOURCE);
if (mSinglePixel)
context.SetDeviceColor(mSinglePixelColor);
else
context.SetSource(mOptSurface);
context.Paint();
mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
if (!mImageSurface || mImageSurface->CairoStatus())
return NS_ERROR_OUT_OF_MEMORY;
} else if (mOptSurface || mSinglePixel) {
// Recover the pixels
mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
if (!mVBuf) {
return NS_ERROR_OUT_OF_MEMORY;
}
mOptSurface = nullptr;
mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
if (!mImageSurface || mImageSurface->CairoStatus())
return NS_ERROR_OUT_OF_MEMORY;
gfxContext context(mImageSurface);
context.SetOperator(gfxContext::OPERATOR_SOURCE);
if (mSinglePixel)
context.SetDeviceColor(mSinglePixelColor);
else
context.SetSource(mOptSurface);
context.Paint();
mOptSurface = nullptr;
#ifdef USE_WIN_SURFACE
mWinSurface = nullptr;
mWinSurface = nullptr;
#endif
#ifdef XP_MACOSX
mQuartzSurface = nullptr;
mQuartzSurface = nullptr;
#endif
}
}
// We might write to the bits in this image surface, so we need to make the
@ -603,6 +671,12 @@ nsresult imgFrame::LockImageData()
mWinSurface->Flush();
#endif
#ifdef XP_MACOSX
if (!mQuartzSurface && !ShouldUseImageSurfaces()) {
mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
}
#endif
return NS_OK;
}
@ -658,6 +732,13 @@ nsresult imgFrame::UnlockImageData()
mQuartzSurface->Flush();
#endif
if (mVBuf && mDiscardable) {
mImageSurface = nullptr;
#ifdef XP_MACOSX
mQuartzSurface = nullptr;
#endif
}
return NS_OK;
}
@ -697,6 +778,12 @@ void imgFrame::ApplyDirtToSurfaces()
}
}
void imgFrame::SetDiscardable()
{
MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
mDiscardable = true;
}
int32_t imgFrame::GetRawTimeout() const
{
return mTimeout;
@ -795,8 +882,8 @@ imgFrame::SizeOfExcludingThisWithComputedFallbackIfHeap(gfxMemoryLocation aLocat
#endif
#ifdef XP_MACOSX
if (mQuartzSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += mSize.width * mSize.height * 4;
} else
n += aMallocSizeOf(mQuartzSurface);
}
#endif
if (mImageSurface && aLocation == mImageSurface->GetMemoryLocation()) {
size_t n2 = 0;
@ -809,6 +896,11 @@ imgFrame::SizeOfExcludingThisWithComputedFallbackIfHeap(gfxMemoryLocation aLocat
n += n2;
}
if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
n += aMallocSizeOf(mVBuf);
n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
}
if (mOptSurface && aLocation == mOptSurface->GetMemoryLocation()) {
size_t n2 = 0;
if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP &&

View File

@ -9,6 +9,7 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/VolatileBuffer.h"
#include "nsRect.h"
#include "nsPoint.h"
#include "nsSize.h"
@ -24,6 +25,21 @@
#include "imgIContainer.h"
#include "gfxColor.h"
/*
* This creates a gfxImageSurface which will unlock the buffer on destruction
*/
class LockedImageSurface
{
public:
static gfxImageSurface *
CreateSurface(mozilla::VolatileBuffer *vbuf,
const gfxIntSize& size,
gfxImageFormat format);
static mozilla::TemporaryRef<mozilla::VolatileBuffer>
AllocateBuffer(const gfxIntSize& size, gfxImageFormat format);
};
class imgFrame
{
public:
@ -72,14 +88,16 @@ public:
nsresult UnlockImageData();
void ApplyDirtToSurfaces();
nsresult GetSurface(gfxASurface **aSurface) const
void SetDiscardable();
nsresult GetSurface(gfxASurface **aSurface)
{
*aSurface = ThebesSurface();
NS_IF_ADDREF(*aSurface);
return NS_OK;
}
nsresult GetPattern(gfxPattern **aPattern) const
nsresult GetPattern(gfxPattern **aPattern)
{
if (mSinglePixel)
*aPattern = new gfxPattern(mSinglePixelColor);
@ -89,7 +107,12 @@ public:
return NS_OK;
}
gfxASurface* ThebesSurface() const
bool IsSinglePixel()
{
return mSinglePixel;
}
gfxASurface* ThebesSurface()
{
if (mOptSurface)
return mOptSurface;
@ -100,7 +123,27 @@ public:
if (mQuartzSurface)
return mQuartzSurface;
#endif
return mImageSurface;
if (mImageSurface)
return mImageSurface;
if (mVBuf) {
mozilla::VolatileBufferPtr<uint8_t> ref(mVBuf);
if (ref.WasBufferPurged())
return nullptr;
gfxImageSurface *sur =
LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
#if defined(XP_MACOSX)
return new gfxQuartzImageSurface(sur);
#else
return sur;
#endif
}
// We can return null here if we're single pixel optimized
// or a paletted image. However, one has to check for paletted
// image data first before attempting to get a surface, so
// this is only valid for single pixel optimized images
MOZ_ASSERT(mSinglePixel, "No image surface and not a single pixel!");
return nullptr;
}
size_t SizeOfExcludingThisWithComputedFallbackIfHeap(
@ -167,6 +210,8 @@ private: // data
/** Indicates how many readers currently have locked this frame */
int32_t mLockCount;
mozilla::RefPtr<mozilla::VolatileBuffer> mVBuf;
gfxImageFormat mFormat;
uint8_t mPaletteDepth;
int8_t mBlendMethod;
@ -174,6 +219,7 @@ private: // data
bool mFormatChanged;
bool mCompositingFailed;
bool mNonPremult;
bool mDiscardable;
/** Have we called DiscardTracker::InformAllocation()? */
bool mInformedDiscardTracker;