Bug 716140 - Implement multithreaded decoding using a thread pool. r=seth

This commit is contained in:
Joe Drew 2013-03-01 18:17:24 -05:00
parent 969dfd9705
commit bf3283de15
6 changed files with 403 additions and 302 deletions

View File

@ -76,7 +76,7 @@ public:
void FlushInvalidations(); void FlushInvalidations();
// We're not COM-y, so we don't get refcounts by default // We're not COM-y, so we don't get refcounts by default
NS_INLINE_DECL_REFCOUNTING(Decoder) NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
/* /*
* State. * State.
@ -97,6 +97,11 @@ public:
mSynchronous = aSynchronous; mSynchronous = aSynchronous;
} }
bool IsSynchronous() const
{
return mSynchronous;
}
void SetObserver(imgDecoderObserver* aObserver) void SetObserver(imgDecoderObserver* aObserver)
{ {
MOZ_ASSERT(aObserver); MOZ_ASSERT(aObserver);

View File

@ -16,9 +16,14 @@
#include "nsAutoPtr.h" #include "nsAutoPtr.h"
#include "nsStringStream.h" #include "nsStringStream.h"
#include "prenv.h" #include "prenv.h"
#include "prsystem.h"
#include "ImageContainer.h" #include "ImageContainer.h"
#include "Layers.h" #include "Layers.h"
#include "nsPresContext.h" #include "nsPresContext.h"
#include "nsThread.h"
#include "nsIThreadPool.h"
#include "nsXPCOMCIDInternal.h"
#include "nsIObserverService.h"
#include "nsPNGDecoder.h" #include "nsPNGDecoder.h"
#include "nsGIFDecoder2.h" #include "nsGIFDecoder2.h"
@ -30,6 +35,7 @@
#include "gfxContext.h" #include "gfxContext.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/StandardInteger.h" #include "mozilla/StandardInteger.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
@ -349,14 +355,14 @@ private:
namespace mozilla { namespace mozilla {
namespace image { namespace image {
/* static */ StaticRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton; /* static */ StaticRefPtr<RasterImage::DecodePool> RasterImage::DecodePool::sSingleton;
static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr; static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
#ifndef DEBUG #ifndef DEBUG
NS_IMPL_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties) NS_IMPL_THREADSAFE_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
#else #else
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties, NS_IMPL_THREADSAFE_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
imgIContainerDebug) imgIContainerDebug)
#endif #endif
//****************************************************************************** //******************************************************************************
@ -368,12 +374,14 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
mAnim(nullptr), mAnim(nullptr),
mLoopCount(-1), mLoopCount(-1),
mLockCount(0), mLockCount(0),
mDecoder(nullptr),
mBytesDecoded(0),
mDecodeCount(0), mDecodeCount(0),
#ifdef DEBUG #ifdef DEBUG
mFramesNotified(0), mFramesNotified(0),
#endif #endif
mDecodingMutex("RasterImage"),
mDecoder(nullptr),
mBytesDecoded(0),
mInDecoder(false),
mHasSize(false), mHasSize(false),
mDecodeOnDraw(false), mDecodeOnDraw(false),
mMultipart(false), mMultipart(false),
@ -381,7 +389,6 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
mHasSourceData(false), mHasSourceData(false),
mDecoded(false), mDecoded(false),
mHasBeenDecoded(false), mHasBeenDecoded(false),
mInDecoder(false),
mAnimationFinished(false), mAnimationFinished(false),
mFinishing(false), mFinishing(false),
mInUpdateImageContainer(false), mInUpdateImageContainer(false),
@ -418,7 +425,8 @@ RasterImage::~RasterImage()
if (mDecoder) { if (mDecoder) {
// Kill off our decode request, if it's pending. (If not, this call is // Kill off our decode request, if it's pending. (If not, this call is
// harmless.) // harmless.)
DecodeWorker::Singleton()->StopDecoding(this); MutexAutoLock lock(mDecodingMutex);
DecodePool::StopDecoding(this);
mDecoder = nullptr; mDecoder = nullptr;
// Unlock the last frame (if we have any). Our invariant is that, while we // Unlock the last frame (if we have any). Our invariant is that, while we
@ -452,7 +460,7 @@ RasterImage::Initialize()
// Create our singletons now, so we don't have to worry about what thread // Create our singletons now, so we don't have to worry about what thread
// they're created on. // they're created on.
DecodeWorker::Singleton(); DecodePool::Singleton();
} }
nsresult nsresult
@ -779,7 +787,7 @@ RasterImage::GetHeight(int32_t *aHeight)
*aHeight = mSize.height; *aHeight = mSize.height;
return NS_OK; return NS_OK;
} }
//****************************************************************************** //******************************************************************************
/* [noscript] readonly attribute nsSize intrinsicSize; */ /* [noscript] readonly attribute nsSize intrinsicSize; */
NS_IMETHODIMP NS_IMETHODIMP
@ -1377,6 +1385,8 @@ RasterImage::ApplyDecodeFlags(uint32_t aNewFlags)
nsresult nsresult
RasterImage::SetSize(int32_t aWidth, int32_t aHeight) RasterImage::SetSize(int32_t aWidth, int32_t aHeight)
{ {
MOZ_ASSERT(NS_IsMainThread());
if (mError) if (mError)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
@ -1532,6 +1542,8 @@ RasterImage::SetFrameAsNonPremult(uint32_t aFrameNum, bool aIsNonPremult)
nsresult nsresult
RasterImage::DecodingComplete() RasterImage::DecodingComplete()
{ {
MOZ_ASSERT(NS_IsMainThread());
if (mError) if (mError)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
@ -1660,6 +1672,8 @@ RasterImage::SetLoopCount(int32_t aLoopCount)
nsresult nsresult
RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount) RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
{ {
MutexAutoLock lock(mDecodingMutex);
if (mError) if (mError)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
@ -1721,6 +1735,9 @@ RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
mInDecoder = true; mInDecoder = true;
mDecoder->FlushInvalidations(); mDecoder->FlushInvalidations();
mInDecoder = false; mInDecoder = false;
rv = FinishedSomeDecoding();
CONTAINER_ENSURE_SUCCESS(rv);
} }
// Otherwise, we're storing data in the source buffer // Otherwise, we're storing data in the source buffer
@ -1731,10 +1748,8 @@ RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
if (!newElem) if (!newElem)
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
// If there's a decoder open, that means we want to do more decoding.
// Wake up the worker.
if (mDecoder) { if (mDecoder) {
DecodeWorker::Singleton()->RequestDecode(this); DecodePool::Singleton()->RequestDecode(this);
} }
} }
@ -1778,6 +1793,8 @@ get_header_str (char *buf, char *data, size_t data_len)
nsresult nsresult
RasterImage::DoImageDataComplete() RasterImage::DoImageDataComplete()
{ {
MOZ_ASSERT(NS_IsMainThread());
if (mError) if (mError)
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
@ -1786,26 +1803,27 @@ RasterImage::DoImageDataComplete()
return NS_OK; return NS_OK;
mHasSourceData = true; mHasSourceData = true;
// This call should come straight from necko - no reentrancy allowed
NS_ABORT_IF_FALSE(!mInDecoder, "Re-entrant call to AddSourceData!");
// If there's a decoder open, synchronously decode the beginning of the image // If there's a decoder open, synchronously decode the beginning of the image
// to check for errors and get the image's size. (If we already have the // to check for errors and get the image's size. (If we already have the
// image's size, this does nothing.) Then kick off an async decode of the // image's size, this does nothing.) Then kick off an async decode of the
// rest of the image. // rest of the image.
if (mDecoder) { if (mDecoder) {
nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this); nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
} }
// If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker {
// finish decoding this image. MutexAutoLock lock(mDecodingMutex);
if (mDecoder) {
DecodeWorker::Singleton()->RequestDecode(this);
}
// Free up any extra space in the backing buffer // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
mSourceData.Compact(); // finish decoding this image.
if (mDecoder) {
DecodePool::Singleton()->RequestDecode(this);
}
// Free up any extra space in the backing buffer
mSourceData.Compact();
}
// Log header information // Log header information
if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) { if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) {
@ -1845,7 +1863,10 @@ RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bo
} }
// We just recorded OnStopRequest; we need to inform our listeners. // We just recorded OnStopRequest; we need to inform our listeners.
FinishedSomeDecoding(); {
MutexAutoLock lock(mDecodingMutex);
FinishedSomeDecoding();
}
return finalStatus; return finalStatus;
} }
@ -1873,6 +1894,8 @@ RasterImage::OnImageDataAvailable(nsIRequest*,
nsresult nsresult
RasterImage::OnNewSourceData() RasterImage::OnNewSourceData()
{ {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv; nsresult rv;
if (mError) if (mError)
@ -2428,6 +2451,8 @@ RasterImage::GetKeys(uint32_t *count, char ***keys)
void void
RasterImage::Discard(bool force) RasterImage::Discard(bool force)
{ {
MOZ_ASSERT(NS_IsMainThread());
// We should be ok for discard // We should be ok for discard
NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!"); NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
@ -2653,7 +2678,7 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
// Kill off our decode request, if it's pending. (If not, this call is // Kill off our decode request, if it's pending. (If not, this call is
// harmless.) // harmless.)
DecodeWorker::Singleton()->StopDecoding(this); DecodePool::StopDecoding(this);
nsresult decoderStatus = decoder->GetDecoderError(); nsresult decoderStatus = decoder->GetDecoderError();
if (NS_FAILED(decoderStatus)) { if (NS_FAILED(decoderStatus)) {
@ -2688,6 +2713,8 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
nsresult nsresult
RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount) RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount)
{ {
mDecodingMutex.AssertCurrentThreadOwns();
// We should have a decoder // We should have a decoder
NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!"); NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
@ -2751,6 +2778,8 @@ RasterImage::StartDecoding()
NS_IMETHODIMP NS_IMETHODIMP
RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType) RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
{ {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv; nsresult rv;
if (mError) if (mError)
@ -2781,7 +2810,7 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
// If we have a size decoder open, make sure we get the size // If we have a size decoder open, make sure we get the size
if (mDecoder && mDecoder->IsSizeDecode()) { if (mDecoder && mDecoder->IsSizeDecode()) {
nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this); nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
// If we didn't get the size out of the image, we won't until we get more // If we didn't get the size out of the image, we won't until we get more
@ -2798,6 +2827,8 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
if (mDecoded) if (mDecoded)
return NS_OK; return NS_OK;
MutexAutoLock lock(mDecodingMutex);
// If we have a size decode open, interrupt it and shut it down; or if // If we have a size decode open, interrupt it and shut it down; or if
// the decoder has different flags than what we need // the decoder has different flags than what we need
if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
@ -2805,7 +2836,7 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
} }
// If we don't have a decoder, create one // If we don't have a decoder, create one
if (!mDecoder) { if (!mDecoder) {
rv = InitDecoder(/* aDoSizeDecode = */ false); rv = InitDecoder(/* aDoSizeDecode = */ false);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
@ -2816,6 +2847,13 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
MOZ_ASSERT(mDecoder); MOZ_ASSERT(mDecoder);
} }
// If we're waiting for decode work to be notified, go ahead and do that.
if (mDecodeRequest &&
mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
nsresult rv = FinishedSomeDecoding();
CONTAINER_ENSURE_SUCCESS(rv);
}
// If we've read all the data we have, we're done // If we've read all the data we have, we're done
if (mHasSourceData && mBytesDecoded == mSourceData.Length()) if (mHasSourceData && mBytesDecoded == mSourceData.Length())
return NS_OK; return NS_OK;
@ -2827,7 +2865,7 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
SAMPLE_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get()); SAMPLE_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get());
mDecoder->SetSynchronous(true); mDecoder->SetSynchronous(true);
DecodeWorker::Singleton()->DecodeABitOf(this); DecodePool::Singleton()->DecodeABitOf(this);
// DecodeABitOf can destroy mDecoder. // DecodeABitOf can destroy mDecoder.
if (mDecoder) { if (mDecoder) {
@ -2836,10 +2874,12 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
return NS_OK; return NS_OK;
} }
// If we get this far, dispatch the worker. We do this instead of starting if (!mDecoded) {
// any immediate decoding to guarantee that all our decode notifications are // If we get this far, dispatch the worker. We do this instead of starting
// dispatched asynchronously, and to ensure we stay responsive. // any immediate decoding to guarantee that all our decode notifications are
DecodeWorker::Singleton()->RequestDecode(this); // dispatched asynchronously, and to ensure we stay responsive.
DecodePool::Singleton()->RequestDecode(this);
}
return NS_OK; return NS_OK;
} }
@ -2848,6 +2888,16 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
nsresult nsresult
RasterImage::SyncDecode() RasterImage::SyncDecode()
{ {
MutexAutoLock imgLock(mDecodingMutex);
if (mDecodeRequest) {
// If the image is waiting for decode work to be notified, go ahead and do that.
if (mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
nsresult rv = FinishedSomeDecoding();
CONTAINER_ENSURE_SUCCESS(rv);
}
}
nsresult rv; nsresult rv;
SAMPLE_LABEL_PRINTF("RasterImage", "SyncDecode", "%s", GetURIString().get());; SAMPLE_LABEL_PRINTF("RasterImage", "SyncDecode", "%s", GetURIString().get());;
@ -2860,7 +2910,7 @@ RasterImage::SyncDecode()
// If we have a size decoder open, make sure we get the size // If we have a size decoder open, make sure we get the size
if (mDecoder && mDecoder->IsSizeDecode()) { if (mDecoder && mDecoder->IsSizeDecode()) {
nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this); nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
// If we didn't get the size out of the image, we won't until we get more // If we didn't get the size out of the image, we won't until we get more
@ -2916,6 +2966,9 @@ RasterImage::SyncDecode()
if (mDecoder) { if (mDecoder) {
mDecoder->SetSynchronous(false); mDecoder->SetSynchronous(false);
// If our decoder's still open, there's still work to be done.
DecodePool::Singleton()->RequestDecode(this);
} }
// All good if no errors! // All good if no errors!
@ -3126,11 +3179,7 @@ RasterImage::Draw(gfxContext *aContext,
// We use !mDecoded && mHasSourceData to mean discarded. // We use !mDecoded && mHasSourceData to mean discarded.
if (!mDecoded && mHasSourceData) { if (!mDecoded && mHasSourceData) {
mDrawStartTime = TimeStamp::Now(); mDrawStartTime = TimeStamp::Now();
// We're drawing this image, so indicate that we should decode it as soon
// as possible.
DecodeWorker::Singleton()->MarkAsASAP(this);
} }
// If a synchronous draw is requested, flush anything that might be sitting around // If a synchronous draw is requested, flush anything that might be sitting around
@ -3204,6 +3253,7 @@ RasterImage::UnlockImage()
PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG, PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
("RasterImage[0x%p] canceling decode because image " ("RasterImage[0x%p] canceling decode because image "
"is now unlocked.", this)); "is now unlocked.", this));
MutexAutoLock lock(mDecodingMutex);
FinishedSomeDecoding(eShutdownIntent_NotNeeded); FinishedSomeDecoding(eShutdownIntent_NotNeeded);
ForceDiscard(); ForceDiscard();
return NS_OK; return NS_OK;
@ -3238,6 +3288,8 @@ RasterImage::DecodeSomeData(uint32_t aMaxBytes)
// We should have a decoder if we get here // We should have a decoder if we get here
NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!"); NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
mDecodingMutex.AssertCurrentThreadOwns();
// First, if we've just been called because we allocated a frame on the main // First, if we've just been called because we allocated a frame on the main
// thread, let the decoder deal with the data it set aside at that time by // thread, let the decoder deal with the data it set aside at that time by
// passing it a null buffer. // passing it a null buffer.
@ -3255,7 +3307,7 @@ RasterImage::DecodeSomeData(uint32_t aMaxBytes)
// write the proper amount of data // write the proper amount of data
uint32_t bytesToDecode = std::min(aMaxBytes, uint32_t bytesToDecode = std::min(aMaxBytes,
mSourceData.Length() - mBytesDecoded); mSourceData.Length() - mBytesDecoded);
nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded, nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
bytesToDecode); bytesToDecode);
@ -3311,6 +3363,7 @@ RasterImage::DoError()
// If we're mid-decode, shut down the decoder. // If we're mid-decode, shut down the decoder.
if (mDecoder) { if (mDecoder) {
MutexAutoLock lock(mDecodingMutex);
FinishedSomeDecoding(eShutdownIntent_Error); FinishedSomeDecoding(eShutdownIntent_Error);
} }
@ -3383,6 +3436,8 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mDecodingMutex.AssertCurrentThreadOwns();
nsRefPtr<DecodeRequest> request; nsRefPtr<DecodeRequest> request;
if (aRequest) { if (aRequest) {
request = aRequest; request = aRequest;
@ -3444,135 +3499,134 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
} }
} }
// Then, tell the observers what happened in the decoder. imgStatusTracker::StatusDiff diff;
// If we have no request, we have not yet created a decoder, but we still
// need to send out notifications.
if (request) { if (request) {
image->mStatusTracker->SyncAndSyncNotifyDifference(request->mStatusTracker); diff = image->mStatusTracker->CalculateAndApplyDifference(request->mStatusTracker);
} else {
image->mStatusTracker->SyncNotifyDecodeState();
} }
// If we were a size decode and a full decode was requested, now's the time. {
if (NS_SUCCEEDED(rv) && done && wasSize && image->mWantFullDecode) { // Notifications can't go out with the decoding lock held.
image->mWantFullDecode = false; MutexAutoUnlock unlock(mDecodingMutex);
// If we're not meant to be storing source data and we just got the size, // Then, tell the observers what happened in the decoder.
// we need to synchronously flush all the data we got to a full decoder. // If we have no request, we have not yet created a decoder, but we still
// When that decoder is shut down, we'll also clear our source data. // need to send out notifications.
if (!image->StoringSourceData()) { if (request) {
rv = image->SyncDecode(); image->mStatusTracker->SyncNotifyDifference(diff);
} else { } else {
rv = image->RequestDecode(); image->mStatusTracker->SyncNotifyDecodeState();
} }
// If we were a size decode and a full decode was requested, now's the time.
if (NS_SUCCEEDED(rv) && done && wasSize && image->mWantFullDecode) {
image->mWantFullDecode = false;
// If we're not meant to be storing source data and we just got the size,
// we need to synchronously flush all the data we got to a full decoder.
// When that decoder is shut down, we'll also clear our source data.
if (!image->StoringSourceData()) {
rv = image->SyncDecode();
} else {
rv = image->RequestDecode();
}
}
} }
return rv; return rv;
} }
/* static */ RasterImage::DecodeWorker* NS_IMPL_THREADSAFE_ISUPPORTS1(RasterImage::DecodePool,
RasterImage::DecodeWorker::Singleton() nsIObserver)
/* static */ RasterImage::DecodePool*
RasterImage::DecodePool::Singleton()
{ {
if (!sSingleton) { if (!sSingleton) {
sSingleton = new DecodeWorker(); MOZ_ASSERT(NS_IsMainThread());
sSingleton = new DecodePool();
ClearOnShutdown(&sSingleton); ClearOnShutdown(&sSingleton);
} }
return sSingleton; return sSingleton;
} }
RasterImage::DecodeWorker::~DecodeWorker() RasterImage::DecodePool::DecodePool()
{ {
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodeWorker on main thread!"); mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
if (mThreadPool) {
mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
mThreadPool->SetThreadLimit(std::max(PR_GetNumberOfProcessors() - 1, 1));
// Shut down all the decoders since we're going away. nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
DecodeRequest* request = mASAPDecodeRequests.getFirst(); if (obsSvc) {
while (request) { obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
request->mImage->FinishedSomeDecoding(eShutdownIntent_NotNeeded); }
request = request->getNext();
}
request = mNormalDecodeRequests.getFirst();
while (request) {
request->mImage->FinishedSomeDecoding(eShutdownIntent_NotNeeded);
request = request->getNext();
} }
} }
void RasterImage::DecodePool::~DecodePool()
RasterImage::DecodeWorker::MarkAsASAP(RasterImage* aImg)
{ {
// We can be marked as ASAP before we've been asked to decode. If we are, MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
// create the request so we have somewhere to write down our status. }
if (!aImg->mDecodeRequest) {
aImg->mDecodeRequest = new DecodeRequest(aImg); NS_IMETHODIMP
RasterImage::DecodePool::Observe(nsISupports *subject, const char *topic,
const PRUnichar *data)
{
NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
if (mThreadPool) {
mThreadPool->Shutdown();
mThreadPool = nullptr;
} }
DecodeRequest* request = aImg->mDecodeRequest; return NS_OK;
// If we're already an ASAP request, there's nothing to do here.
if (request->mIsASAP) {
return;
}
request->mIsASAP = true;
if (request->isInList()) {
// If the decode request is in a list, it must be in the normal decode
// requests list -- if it had been in the ASAP list, then mIsASAP would
// have been true above. Move the request to the ASAP list.
request->removeFrom(mNormalDecodeRequests);
mASAPDecodeRequests.insertBack(request);
// Since request is in a list, one of the decode worker's lists is
// non-empty, so the worker should be pending in the event loop.
//
// (Note that this invariant only holds while we are not in Run(), because
// DecodeSomeOfImage adds requests to the decode worker using
// AddDecodeRequest, not RequestDecode, and AddDecodeRequest does not call
// EnsurePendingInEventLoop. Therefore, it is an error to call MarkAsASAP
// from within DecodeWorker::Run.)
MOZ_ASSERT(mPendingInEventLoop);
}
} }
void void
RasterImage::DecodeWorker::AddDecodeRequest(DecodeRequest* aRequest, uint32_t bytesToDecode) RasterImage::DecodePool::RequestDecode(RasterImage* aImg)
{
if (aRequest->isInList()) {
// The image is already in our list of images to decode, so we don't have
// to do anything here.
return;
}
aRequest->mBytesToDecode = bytesToDecode;
if (aRequest->mIsASAP) {
mASAPDecodeRequests.insertBack(aRequest);
} else {
mNormalDecodeRequests.insertBack(aRequest);
}
}
void
RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
{ {
MOZ_ASSERT(aImg->mDecoder); MOZ_ASSERT(aImg->mDecoder);
MOZ_ASSERT(NS_IsMainThread());
aImg->mDecodingMutex.AssertCurrentThreadOwns();
// If we're currently waiting on a new frame for this image, we can't do any // If we're currently waiting on a new frame for this image, we can't do any
// decoding. // decoding.
if (!aImg->mDecoder->NeedsNewFrame()) { if (!aImg->mDecoder->NeedsNewFrame()) {
AddDecodeRequest(aImg->mDecodeRequest, aImg->mSourceData.Length() - aImg->mBytesDecoded); // No matter whether this is currently being decoded, we need to update the
EnsurePendingInEventLoop(); // number of bytes we want it to decode.
aImg->mDecodeRequest->mBytesToDecode = aImg->mSourceData.Length() - aImg->mBytesDecoded;
if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_PENDING ||
aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_ACTIVE) {
// The image is already in our list of images to decode, or currently being
// decoded, so we don't have to do anything else.
return;
}
aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_PENDING;
nsRefPtr<DecodeJob> job = new DecodeJob(aImg->mDecodeRequest, aImg);
if (!mThreadPool) {
NS_DispatchToMainThread(job);
} else {
mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
}
} }
} }
void void
RasterImage::DecodeWorker::DecodeABitOf(RasterImage* aImg) RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
aImg->mDecodingMutex.AssertCurrentThreadOwns();
if (aImg->mDecodeRequest) {
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
aImg->FinishedSomeDecoding();
}
}
DecodeSomeOfImage(aImg); DecodeSomeOfImage(aImg);
@ -3594,98 +3648,90 @@ RasterImage::DecodeWorker::DecodeABitOf(RasterImage* aImg)
} }
} }
void /* static */ void
RasterImage::DecodeWorker::EnsurePendingInEventLoop() RasterImage::DecodePool::StopDecoding(RasterImage* aImg)
{ {
if (!mPendingInEventLoop) { aImg->mDecodingMutex.AssertCurrentThreadOwns();
mPendingInEventLoop = true;
NS_DispatchToCurrentThread(this);
}
}
void
RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
{
// If we haven't got a decode request, we're not currently decoding. (Having // If we haven't got a decode request, we're not currently decoding. (Having
// a decode request doesn't imply we *are* decoding, though.) // a decode request doesn't imply we *are* decoding, though.)
if (aImg->mDecodeRequest) { if (aImg->mDecodeRequest) {
if (aImg->mDecodeRequest->isInList()) { aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_INACTIVE;
aImg->mDecodeRequest->remove();
}
} }
} }
NS_IMETHODIMP NS_IMETHODIMP
RasterImage::DecodeWorker::Run() RasterImage::DecodePool::DecodeJob::Run()
{ {
// We just got called back by the event loop; therefore, we're no longer MutexAutoLock imglock(mImage->mDecodingMutex);
// pending.
mPendingInEventLoop = false;
TimeStamp eventStart = TimeStamp::Now(); // If we were interrupted, we shouldn't do any work.
if (mRequest->mRequestStatus == DecodeRequest::REQUEST_STOPPED) {
// Now decode until we either run out of time or run out of images. return NS_OK;
do {
// Try to get an ASAP request to handle. If there isn't one, try to get a
// normal request. If no normal request is pending either, then we're done
// here.
DecodeRequest* request = mASAPDecodeRequests.popFirst();
if (!request)
request = mNormalDecodeRequests.popFirst();
if (!request)
break;
// This has to be a strong pointer, because DecodeSomeOfImage may destroy
// image->mDecoder, which may be holding the only other reference to image.
nsRefPtr<RasterImage> image = request->mImage;
uint32_t oldFrameCount = image->mDecoder->GetFrameCount();
uint32_t oldByteCount = image->mBytesDecoded;
DecodeSomeOfImage(image, DECODE_TYPE_NORMAL, request->mBytesToDecode);
uint32_t bytesDecoded = image->mBytesDecoded - oldByteCount;
// If the decoder needs a new frame, enqueue an event to get it; that event
// will enqueue another decode request when it's done.
if (image->mDecoder && image->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::GetNewFrame(image);
}
// If we aren't yet finished decoding and we have more data in hand, add
// this request to the back of the list.
else if (image->mDecoder &&
!image->mError &&
!image->IsDecodeFinished() &&
bytesDecoded < request->mBytesToDecode) {
AddDecodeRequest(request, request->mBytesToDecode - bytesDecoded);
// If we have a new frame, let everybody know about it.
if (image->mDecoder->GetFrameCount() != oldFrameCount) {
DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
}
} else {
// Nothing more for us to do - let everyone know what happened.
DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
}
} while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
// If decode requests are pending, re-post ourself to the event loop.
if (!mASAPDecodeRequests.isEmpty() || !mNormalDecodeRequests.isEmpty()) {
EnsurePendingInEventLoop();
} }
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY_US, // If someone came along and synchronously decoded us, there's nothing for us to do.
uint32_t((TimeStamp::Now() - eventStart).ToMicroseconds())); if (!mRequest->mAllocatedNewFrame && (!mImage->mDecoder || mImage->IsDecodeFinished() ||
mRequest->mRequestStatus != DecodeRequest::REQUEST_PENDING)) {
return NS_OK;
}
mRequest->mRequestStatus = DecodeRequest::REQUEST_ACTIVE;
uint32_t oldByteCount = mImage->mBytesDecoded;
DecodeType type = DECODE_TYPE_UNTIL_DONE_BYTES;
// Multithreaded decoding can be disabled. If we've done so, we don't want to
// monopolize the main thread, and will allow a timeout in DecodeSomeOfImage.
if (NS_IsMainThread()) {
type = DECODE_TYPE_UNTIL_TIME;
}
DecodePool::Singleton()->DecodeSomeOfImage(mImage, type, mRequest->mBytesToDecode);
uint32_t bytesDecoded = mImage->mBytesDecoded - oldByteCount;
mRequest->mRequestStatus = DecodeRequest::REQUEST_WORK_DONE;
// If the decoder needs a new frame, enqueue an event to get it; that event
// will enqueue another decode request when it's done.
if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::GetNewFrame(mImage);
}
// If we aren't yet finished decoding and we have more data in hand, add
// this request to the back of the list.
else if (mImage->mDecoder &&
!mImage->mError &&
!mImage->IsDecodeFinished() &&
bytesDecoded < mRequest->mBytesToDecode) {
DecodePool::Singleton()->RequestDecode(mImage);
} else {
// Nothing more for us to do - let everyone know what happened.
DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
}
return NS_OK; return NS_OK;
} }
nsresult nsresult
RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg) RasterImage::DecodePool::DecodeUntilSizeAvailable(RasterImage* aImg)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock imgLock(aImg->mDecodingMutex);
if (aImg->mDecodeRequest) {
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
nsresult rv = aImg->FinishedSomeDecoding();
if (NS_FAILED(rv)) {
aImg->DoError();
return rv;
}
}
}
nsresult rv = DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE); nsresult rv = DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
@ -3703,12 +3749,13 @@ RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
} }
nsresult nsresult
RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg, RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */, DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */,
uint32_t bytesToDecode /* = 0 */) uint32_t bytesToDecode /* = 0 */)
{ {
NS_ABORT_IF_FALSE(aImg->mInitialized, NS_ABORT_IF_FALSE(aImg->mInitialized,
"Worker active for uninitialized container!"); "Worker active for uninitialized container!");
aImg->mDecodingMutex.AssertCurrentThreadOwns();
// If an error is flagged, it probably happened while we were waiting // If an error is flagged, it probably happened while we were waiting
// in the event queue. // in the event queue.
@ -3720,9 +3767,17 @@ RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg,
if (!aImg->mDecoder || aImg->mDecoded) if (!aImg->mDecoder || aImg->mDecoded)
return NS_OK; return NS_OK;
// If we're currently waiting on a new frame for this image, we can't do any // If we're doing synchronous decodes, and we're waiting on a new frame for
// decoding right now. // this image, get it now.
if (aImg->mDecoder->NeedsNewFrame()) { if (aImg->mDecoder->IsSynchronous() && aImg->mDecoder->NeedsNewFrame()) {
MOZ_ASSERT(NS_IsMainThread());
aImg->mDecoder->AllocateFrame();
aImg->mDecodeRequest->mAllocatedNewFrame = true;
}
// If we're not synchronous, we can't allocate a frame right now.
else if (aImg->mDecoder->NeedsNewFrame()) {
return NS_OK; return NS_OK;
} }
@ -3773,7 +3828,7 @@ RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg,
// Yield if we've been decoding for too long. We check this _after_ decoding // Yield if we've been decoding for too long. We check this _after_ decoding
// a chunk to ensure that we don't yield without doing any decoding. // a chunk to ensure that we don't yield without doing any decoding.
if (TimeStamp::Now() >= deadline) if (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline)
break; break;
} }
@ -3818,6 +3873,8 @@ RasterImage::DecodeDoneWorker::DecodeDoneWorker(RasterImage* image, DecodeReques
void void
RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request) RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request)
{ {
image->mDecodingMutex.AssertCurrentThreadOwns();
nsCOMPtr<DecodeDoneWorker> worker = new DecodeDoneWorker(image, request); nsCOMPtr<DecodeDoneWorker> worker = new DecodeDoneWorker(image, request);
NS_DispatchToMainThread(worker); NS_DispatchToMainThread(worker);
} }
@ -3825,12 +3882,15 @@ RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, De
NS_IMETHODIMP NS_IMETHODIMP
RasterImage::DecodeDoneWorker::Run() RasterImage::DecodeDoneWorker::Run()
{ {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mImage->mDecodingMutex);
mImage->FinishedSomeDecoding(eShutdownIntent_Done, mRequest); mImage->FinishedSomeDecoding(eShutdownIntent_Done, mRequest);
// If we didn't finish decoding yet, try again // If we didn't finish decoding yet, try again
if (mImage->mDecoder && !mImage->IsDecodeFinished() && if (mImage->mDecoder && !mImage->IsDecodeFinished() &&
mImage->mSourceData.Length() > mImage->mBytesDecoded) { mImage->mSourceData.Length() > mImage->mBytesDecoded) {
DecodeWorker::Singleton()->RequestDecode(mImage); DecodePool::Singleton()->RequestDecode(mImage);
} }
return NS_OK; return NS_OK;
@ -3851,6 +3911,7 @@ RasterImage::FrameNeededWorker::GetNewFrame(RasterImage* image)
NS_IMETHODIMP NS_IMETHODIMP
RasterImage::FrameNeededWorker::Run() RasterImage::FrameNeededWorker::Run()
{ {
MutexAutoLock lock(mImage->mDecodingMutex);
nsresult rv = NS_OK; nsresult rv = NS_OK;
// If we got a synchronous decode in the mean time, we don't need to do // If we got a synchronous decode in the mean time, we don't need to do
@ -3861,7 +3922,8 @@ RasterImage::FrameNeededWorker::Run()
} }
if (NS_SUCCEEDED(rv) && mImage->mDecoder) { if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
DecodeWorker::Singleton()->RequestDecode(mImage); // By definition, we're not done decoding, so enqueue us for more decoding.
DecodePool::Singleton()->RequestDecode(mImage);
} }
return NS_OK; return NS_OK;

View File

@ -18,7 +18,6 @@
#define mozilla_imagelib_RasterImage_h_ #define mozilla_imagelib_RasterImage_h_
#include "Image.h" #include "Image.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "imgIContainer.h" #include "imgIContainer.h"
#include "nsIProperties.h" #include "nsIProperties.h"
@ -28,18 +27,20 @@
#include "imgFrame.h" #include "imgFrame.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "DiscardTracker.h" #include "DiscardTracker.h"
#include "nsISupportsImpl.h"
#include "mozilla/TimeStamp.h" #include "mozilla/TimeStamp.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "mozilla/LinkedList.h" #include "mozilla/LinkedList.h"
#include "mozilla/StaticPtr.h" #include "mozilla/StaticPtr.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "mozilla/RefPtr.h" #include "mozilla/Mutex.h"
#include "gfx2DGlue.h" #include "gfx2DGlue.h"
#ifdef DEBUG #ifdef DEBUG
#include "imgIContainerDebug.h" #include "imgIContainerDebug.h"
#endif #endif
class nsIInputStream; class nsIInputStream;
class nsIThreadPool;
#define NS_RASTERIMAGE_CID \ #define NS_RASTERIMAGE_CID \
{ /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */ \ { /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */ \
@ -387,25 +388,23 @@ private:
}; };
/** /**
* DecodeWorker keeps a linked list of DecodeRequests to keep track of the
* images it needs to decode.
*
* Each RasterImage has a pointer to one or zero heap-allocated * Each RasterImage has a pointer to one or zero heap-allocated
* DecodeRequests. * DecodeRequests.
*/ */
struct DecodeRequest : public LinkedListElement<DecodeRequest>, struct DecodeRequest
public RefCounted<DecodeRequest>
{ {
DecodeRequest(RasterImage* aImage) DecodeRequest(RasterImage* aImage)
: mImage(aImage) : mImage(aImage)
, mBytesToDecode(0) , mBytesToDecode(0)
, mRequestStatus(REQUEST_INACTIVE)
, mChunkCount(0) , mChunkCount(0)
, mAllocatedNewFrame(false) , mAllocatedNewFrame(false)
, mIsASAP(false)
{ {
mStatusTracker = aImage->mStatusTracker->CloneForRecording(); mStatusTracker = aImage->mStatusTracker->CloneForRecording();
} }
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodeRequest)
// The status tracker that is associated with a given decode request, to // The status tracker that is associated with a given decode request, to
// ensure their lifetimes are linked. // ensure their lifetimes are linked.
nsRefPtr<imgStatusTracker> mStatusTracker; nsRefPtr<imgStatusTracker> mStatusTracker;
@ -414,6 +413,15 @@ private:
uint32_t mBytesToDecode; uint32_t mBytesToDecode;
enum DecodeRequestStatus
{
REQUEST_INACTIVE,
REQUEST_PENDING,
REQUEST_ACTIVE,
REQUEST_WORK_DONE,
REQUEST_STOPPED
} mRequestStatus;
/* Keeps track of how much time we've burned decoding this particular decode /* Keeps track of how much time we've burned decoding this particular decode
* request. */ * request. */
TimeDuration mDecodeTime; TimeDuration mDecodeTime;
@ -424,35 +432,31 @@ private:
/* True if a new frame has been allocated, but DecodeSomeData hasn't yet /* True if a new frame has been allocated, but DecodeSomeData hasn't yet
* been called to flush data to it */ * been called to flush data to it */
bool mAllocatedNewFrame; bool mAllocatedNewFrame;
/* True if we need to handle this decode as soon as possible. */
bool mIsASAP;
}; };
/* /*
* DecodeWorker is a singleton class we use when decoding large images. * DecodePool is a singleton class we use when decoding large images.
* *
* When we wish to decode an image larger than * When we wish to decode an image larger than
* image.mem.max_bytes_for_sync_decode, we call DecodeWorker::RequestDecode() * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode()
* for the image. This adds the image to a queue of pending requests and posts * for the image. This adds the image to a queue of pending requests and posts
* the DecodeWorker singleton to the event queue, if it's not already pending * the DecodePool singleton to the event queue, if it's not already pending
* there. * there.
* *
* When the DecodeWorker is run from the event queue, it decodes the image (and * When the DecodePool is run from the event queue, it decodes the image (and
* all others it's managing) in chunks, periodically yielding control back to * all others it's managing) in chunks, periodically yielding control back to
* the event loop. * the event loop.
*
* An image being decoded may have one of two priorities: normal or ASAP. ASAP
* images are always decoded before normal images. (We currently give ASAP
* priority to images which appear onscreen but are not yet decoded.)
*/ */
class DecodeWorker : public nsRunnable class DecodePool : public nsIObserver
{ {
public: public:
static DecodeWorker* Singleton(); NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
static DecodePool* Singleton();
/** /**
* Ask the DecodeWorker to asynchronously decode this image. * Ask the DecodePool to asynchronously decode this image.
*/ */
void RequestDecode(RasterImage* aImg); void RequestDecode(RasterImage* aImg);
@ -463,23 +467,13 @@ private:
void DecodeABitOf(RasterImage* aImg); void DecodeABitOf(RasterImage* aImg);
/** /**
* Give this image ASAP priority; it will be decoded before all non-ASAP * Ask the DecodePool to stop decoding this image. Internally, we also
* images. You can call MarkAsASAP before or after you call RequestDecode
* for the image, but if you MarkAsASAP before you call RequestDecode, you
* still need to call RequestDecode.
*
* StopDecoding() resets the image's ASAP flag.
*/
void MarkAsASAP(RasterImage* aImg);
/**
* Ask the DecodeWorker to stop decoding this image. Internally, we also
* call this function when we finish decoding an image. * call this function when we finish decoding an image.
* *
* Since the DecodeWorker keeps raw pointers to RasterImages, make sure you * Since the DecodePool keeps raw pointers to RasterImages, make sure you
* call this before a RasterImage is destroyed! * call this before a RasterImage is destroyed!
*/ */
void StopDecoding(RasterImage* aImg); static void StopDecoding(RasterImage* aImg);
/** /**
* Synchronously decode the beginning of the image until we run out of * Synchronously decode the beginning of the image until we run out of
@ -491,56 +485,56 @@ private:
*/ */
nsresult DecodeUntilSizeAvailable(RasterImage* aImg); nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
NS_IMETHOD Run(); virtual ~DecodePool();
virtual ~DecodeWorker();
private: /* statics */ private: /* statics */
static StaticRefPtr<DecodeWorker> sSingleton; static StaticRefPtr<DecodePool> sSingleton;
private: /* methods */ private: /* methods */
DecodeWorker() DecodePool();
: mPendingInEventLoop(false)
{}
/* Post ourselves to the event loop if we're not currently pending. */
void EnsurePendingInEventLoop();
/* Add the given request to the appropriate list of decode requests, but
* don't ensure that we're pending in the event loop. */
void AddDecodeRequest(DecodeRequest* aRequest, uint32_t bytesToDecode);
enum DecodeType { enum DecodeType {
DECODE_TYPE_NORMAL, DECODE_TYPE_UNTIL_TIME,
DECODE_TYPE_UNTIL_SIZE DECODE_TYPE_UNTIL_SIZE,
DECODE_TYPE_UNTIL_DONE_BYTES
}; };
/* Decode some chunks of the given image. If aDecodeType is UNTIL_SIZE, /* Decode some chunks of the given image. If aDecodeType is UNTIL_SIZE,
* decode until we have the image's size, then stop. If bytesToDecode is * decode until we have the image's size, then stop. If bytesToDecode is
* non-0, at most bytesToDecode bytes will be decoded. */ * non-0, at most bytesToDecode bytes will be decoded. if aDecodeType is
* UNTIL_DONE_BYTES, decode until all bytesToDecode bytes are decoded.
*/
nsresult DecodeSomeOfImage(RasterImage* aImg, nsresult DecodeSomeOfImage(RasterImage* aImg,
DecodeType aDecodeType = DECODE_TYPE_NORMAL, DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
uint32_t bytesToDecode = 0); uint32_t bytesToDecode = 0);
/* Create a new DecodeRequest suitable for doing some decoding and set it /* A decode job dispatched to a thread pool by DecodePool.
* as aImg's mDecodeRequest. */ */
void CreateRequestForImage(RasterImage* aImg); class DecodeJob : public nsRunnable
{
public:
DecodeJob(DecodeRequest* aRequest, RasterImage* aImg)
: mRequest(aRequest)
, mImage(aImg)
{}
NS_IMETHOD Run();
private:
nsRefPtr<DecodeRequest> mRequest;
nsRefPtr<RasterImage> mImage;
};
private: /* members */ private: /* members */
LinkedList<DecodeRequest> mASAPDecodeRequests; nsCOMPtr<nsIThreadPool> mThreadPool;
LinkedList<DecodeRequest> mNormalDecodeRequests;
/* True if we've posted ourselves to the event loop and expect Run() to
* be called sometime in the future. */
bool mPendingInEventLoop;
}; };
class DecodeDoneWorker : public nsRunnable class DecodeDoneWorker : public nsRunnable
{ {
public: public:
/** /**
* Called by the DecodeWorker with an image when it's done some significant * Called by the DecodePool with an image when it's done some significant
* portion of decoding that needs to be notified about. * portion of decoding that needs to be notified about.
* *
* Ensures the decode state accumulated by the decoding process gets * Ensures the decode state accumulated by the decoding process gets
@ -563,7 +557,7 @@ private:
{ {
public: public:
/** /**
* Called by the DecodeWorker with an image when it's been told by the * Called by the DecodeJob with an image when it's been told by the
* decoder that it needs a new frame to be allocated on the main thread. * decoder that it needs a new frame to be allocated on the main thread.
* *
* Dispatches an event to do so, which will further dispatch a * Dispatches an event to do so, which will further dispatch a
@ -754,16 +748,10 @@ private: // data
DiscardTracker::Node mDiscardTrackerNode; DiscardTracker::Node mDiscardTrackerNode;
// Source data members // Source data members
FallibleTArray<char> mSourceData;
nsCString mSourceDataMimeType; nsCString mSourceDataMimeType;
friend class DiscardTracker; friend class DiscardTracker;
// Decoder and friends
nsRefPtr<Decoder> mDecoder;
nsRefPtr<DecodeRequest> mDecodeRequest;
uint32_t mBytesDecoded;
// How many times we've decoded this image. // How many times we've decoded this image.
// This is currently only used for statistics // This is currently only used for statistics
int32_t mDecodeCount; int32_t mDecodeCount;
@ -778,6 +766,22 @@ private: // data
uint32_t mFramesNotified; uint32_t mFramesNotified;
#endif #endif
// Below are the pieces of data that can be accessed on more than one thread
// at once, and hence need to be locked by mDecodingMutex.
// BEGIN LOCKED MEMBER VARIABLES
mozilla::Mutex mDecodingMutex;
FallibleTArray<char> mSourceData;
// Decoder and friends
nsRefPtr<Decoder> mDecoder;
nsRefPtr<DecodeRequest> mDecodeRequest;
uint32_t mBytesDecoded;
// END LOCKED MEMBER VARIABLES
bool mInDecoder;
// Boolean flags (clustered together to conserve space): // Boolean flags (clustered together to conserve space):
bool mHasSize:1; // Has SetSize() been called? bool mHasSize:1; // Has SetSize() been called?
bool mDecodeOnDraw:1; // Decoding on draw? bool mDecodeOnDraw:1; // Decoding on draw?
@ -789,7 +793,6 @@ private: // data
bool mDecoded:1; bool mDecoded:1;
bool mHasBeenDecoded:1; bool mHasBeenDecoded:1;
bool mInDecoder:1;
// Whether the animation can stop, due to running out // Whether the animation can stop, due to running out
// of frames, or no more owning request // of frames, or no more owning request

View File

@ -388,7 +388,8 @@ VectorImage::OnImageDataComplete(nsIRequest* aRequest,
nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording(); nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
imgDecoderObserver* observer = clone->GetDecoderObserver(); imgDecoderObserver* observer = clone->GetDecoderObserver();
observer->OnStopRequest(aLastPart, finalStatus); observer->OnStopRequest(aLastPart, finalStatus);
mStatusTracker->SyncAndSyncNotifyDifference(clone); imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
mStatusTracker->SyncNotifyDifference(diff);
} }
return finalStatus; return finalStatus;
} }
@ -846,7 +847,8 @@ VectorImage::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording(); nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
imgDecoderObserver* observer = clone->GetDecoderObserver(); imgDecoderObserver* observer = clone->GetDecoderObserver();
observer->OnStartDecode(); observer->OnStartDecode();
mStatusTracker->SyncAndSyncNotifyDifference(clone); imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
mStatusTracker->SyncNotifyDifference(diff);
} }
// Create a listener to wait until the SVG document is fully loaded, which // Create a listener to wait until the SVG document is fully loaded, which
@ -933,7 +935,8 @@ VectorImage::OnSVGDocumentLoaded()
observer->OnStopFrame(); observer->OnStopFrame();
observer->OnStopDecode(NS_OK); // Unblock page load. observer->OnStopDecode(NS_OK); // Unblock page load.
mStatusTracker->SyncAndSyncNotifyDifference(clone); imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
mStatusTracker->SyncNotifyDifference(diff);
} }
EvaluateAnimation(); EvaluateAnimation();
@ -955,7 +958,8 @@ VectorImage::OnSVGDocumentError()
// Unblock page load. // Unblock page load.
observer->OnStopDecode(NS_ERROR_FAILURE); observer->OnStopDecode(NS_ERROR_FAILURE);
mStatusTracker->SyncAndSyncNotifyDifference(clone); imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
mStatusTracker->SyncNotifyDifference(diff);
} }
} }

View File

@ -517,24 +517,26 @@ imgStatusTracker::SyncNotifyState(nsTObserverArray<imgRequestProxy*>& proxies,
} }
} }
void imgStatusTracker::StatusDiff
imgStatusTracker::SyncAndSyncNotifyDifference(imgStatusTracker* other) imgStatusTracker::CalculateAndApplyDifference(imgStatusTracker* other)
{ {
LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndSyncNotifyDifference"); LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndCalculateDifference");
// We must not modify or notify for the start-load state, which happens from Necko callbacks. // We must not modify or notify for the start-load state, which happens from Necko callbacks.
uint32_t loadState = mState & stateRequestStarted; uint32_t loadState = mState & stateRequestStarted;
uint32_t diffState = ~mState & other->mState & ~stateRequestStarted;
bool unblockedOnload = mState & stateBlockingOnload && !(other->mState & stateBlockingOnload); StatusDiff diff;
bool foundError = (mImageStatus != imgIRequest::STATUS_ERROR) && (other->mImageStatus == imgIRequest::STATUS_ERROR); diff.mDiffState = ~mState & other->mState & ~stateRequestStarted;
diff.mUnblockedOnload = mState & stateBlockingOnload && !(other->mState & stateBlockingOnload);
diff.mFoundError = (mImageStatus != imgIRequest::STATUS_ERROR) && (other->mImageStatus == imgIRequest::STATUS_ERROR);
// Now that we've calculated the difference in state, synchronize our state // Now that we've calculated the difference in state, synchronize our state
// with the other tracker. // with the other tracker.
// First, actually synchronize our state. // First, actually synchronize our state.
mInvalidRect = mInvalidRect.Union(other->mInvalidRect); diff.mInvalidRect = mInvalidRect.Union(other->mInvalidRect);
mState |= diffState | loadState; mState |= diff.mDiffState | loadState;
if (unblockedOnload) { if (diff.mUnblockedOnload) {
mState &= ~stateBlockingOnload; mState &= ~stateBlockingOnload;
} }
mImageStatus = other->mImageStatus; mImageStatus = other->mImageStatus;
@ -552,9 +554,21 @@ imgStatusTracker::SyncAndSyncNotifyDifference(imgStatusTracker* other)
} }
} }
SyncNotifyState(mConsumers, !!mImage, diffState, mInvalidRect, mHadLastPart); // Reset the invalid rectangles for another go.
other->mInvalidRect.SetEmpty();
mInvalidRect.SetEmpty();
if (unblockedOnload) { return diff;
}
void
imgStatusTracker::SyncNotifyDifference(imgStatusTracker::StatusDiff diff)
{
LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference");
SyncNotifyState(mConsumers, !!mImage, diff.mDiffState, diff.mInvalidRect, mHadLastPart);
if (diff.mUnblockedOnload) {
nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers); nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
while (iter.HasMore()) { while (iter.HasMore()) {
// Hold on to a reference to this proxy, since notifying the state can // Hold on to a reference to this proxy, since notifying the state can
@ -567,11 +581,7 @@ imgStatusTracker::SyncAndSyncNotifyDifference(imgStatusTracker* other)
} }
} }
// Reset the invalid rectangles for another go. if (diff.mFoundError) {
other->mInvalidRect.SetEmpty();
mInvalidRect.SetEmpty();
if (foundError) {
FireFailureNotification(); FireFailureNotification();
} }
} }

View File

@ -23,11 +23,11 @@ class Image;
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsTObserverArray.h" #include "nsTObserverArray.h"
#include "nsIRunnable.h" #include "nsIRunnable.h"
#include "nscore.h" #include "nscore.h"
#include "imgDecoderObserver.h" #include "imgDecoderObserver.h"
#include "nsISupportsImpl.h"
enum { enum {
stateRequestStarted = 1u << 0, stateRequestStarted = 1u << 0,
@ -51,9 +51,11 @@ enum {
* and the notifications will be replayed to the proxy asynchronously. * and the notifications will be replayed to the proxy asynchronously.
*/ */
class imgStatusTracker : public mozilla::RefCounted<imgStatusTracker> class imgStatusTracker
{ {
public: public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgStatusTracker)
// aImage is the image that this status tracker will pass to the // aImage is the image that this status tracker will pass to the
// imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be
// alive as long as this instance is, because we hold a weak reference to it. // alive as long as this instance is, because we hold a weak reference to it.
@ -193,7 +195,22 @@ public:
inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); } inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }
imgStatusTracker* CloneForRecording(); imgStatusTracker* CloneForRecording();
void SyncAndSyncNotifyDifference(imgStatusTracker* other);
struct StatusDiff
{
uint32_t mDiffState;
bool mUnblockedOnload;
bool mFoundError;
nsIntRect mInvalidRect;
};
// Calculate the difference between this and other, apply that difference to
// ourselves, and return it for passing to SyncNotifyDifference.
StatusDiff CalculateAndApplyDifference(imgStatusTracker* other);
// Notify for the difference found in CalculateAndApplyDifference. No
// decoding locks may be held.
void SyncNotifyDifference(StatusDiff diff);
nsIntRect GetInvalidRect() const { return mInvalidRect; } nsIntRect GetInvalidRect() const { return mInvalidRect; }