Bug 1081012 - Move DecodePool and related helpers out of RasterImage. r=tn

This commit is contained in:
Seth Fowler 2014-11-18 18:17:17 -08:00
parent 8a2d5edd9f
commit dc37edcda5
8 changed files with 715 additions and 705 deletions

512
image/src/DecodePool.cpp Normal file
View File

@ -0,0 +1,512 @@
/* -*- 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 "DecodePool.h"
#include <algorithm>
#include "mozilla/ClearOnShutdown.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsIThreadPool.h"
#include "nsXPCOMCIDInternal.h"
#include "prsystem.h"
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#endif
#include "gfxPrefs.h"
#include "Decoder.h"
#include "RasterImage.h"
using std::max;
using std::min;
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Helper runnables.
///////////////////////////////////////////////////////////////////////////////
class NotifyProgressWorker : public nsRunnable
{
public:
/**
* Called by the DecodePool when it's done some significant portion of
* decoding, so that progress can be recorded and notifications can be sent.
*/
static void Dispatch(RasterImage* aImage)
{
nsCOMPtr<nsIRunnable> worker = new NotifyProgressWorker(aImage);
NS_DispatchToMainThread(worker);
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
mImage->FinishedSomeDecoding(ShutdownReason::DONE);
return NS_OK;
}
private:
explicit NotifyProgressWorker(RasterImage* aImage)
: mImage(aImage)
{ }
nsRefPtr<RasterImage> mImage;
};
class FrameNeededWorker : public nsRunnable
{
public:
/**
* Called when an off-main-thread decoder needs a new frame to be allocated on
* the main thread.
*
* After allocating the new frame, the worker will call RequestDecode to
* continue decoding.
*/
static void Dispatch(RasterImage* aImage)
{
nsCOMPtr<nsIRunnable> worker = new FrameNeededWorker(aImage);
NS_DispatchToMainThread(worker);
}
NS_IMETHOD Run() MOZ_OVERRIDE
{
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
nsresult rv = NS_OK;
// If we got a synchronous decode in the mean time, we don't need to do
// anything.
if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
rv = mImage->mDecoder->AllocateFrame();
}
if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
// By definition, we're not done decoding, so enqueue us for more decoding.
DecodePool::Singleton()->RequestDecode(mImage);
}
return NS_OK;
}
private:
explicit FrameNeededWorker(RasterImage* aImage)
: mImage(aImage)
{ }
nsRefPtr<RasterImage> mImage;
};
class DecodeWorker : public nsRunnable
{
public:
DecodeWorker(RasterImage* aImage)
: mImage(aImage)
{ }
NS_IMETHOD Run() MOZ_OVERRIDE
{
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
// If we were interrupted, we shouldn't do any work.
if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
NotifyProgressWorker::Dispatch(mImage);
return NS_OK;
}
// If someone came along and synchronously decoded us, there's nothing for us to do.
if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
NotifyProgressWorker::Dispatch(mImage);
return NS_OK;
}
// If we're a decode job that's been enqueued since a previous decode that
// still needs a new frame, we can't do anything. Wait until the
// FrameNeededWorker enqueues another frame.
if (mImage->mDecoder->NeedsNewFrame()) {
return NS_OK;
}
mImage->mDecodeStatus = DecodeStatus::ACTIVE;
size_t oldByteCount = mImage->mDecoder->BytesDecoded();
// Multithreaded decoding can be disabled. If we've done so, we don't want
// to monopolize the main thread, and will allow a timeout.
DecodeUntil type = NS_IsMainThread() ? DecodeUntil::TIME
: DecodeUntil::DONE_BYTES;
size_t maxBytes = mImage->mSourceData.Length() -
mImage->mDecoder->BytesDecoded();
DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeStrategy::ASYNC,
type, maxBytes);
size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
mImage->mDecodeStatus = DecodeStatus::WORK_DONE;
if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
// The decoder needs a new frame. Enqueue an event to get it; that event
// will enqueue another decode request when it's done.
FrameNeededWorker::Dispatch(mImage);
} else if (mImage->mDecoder &&
!mImage->mError &&
!mImage->mPendingError &&
!mImage->IsDecodeFinished() &&
bytesDecoded < maxBytes &&
bytesDecoded > 0) {
// We aren't finished decoding, and we have more data, so add this request
// to the back of the list.
DecodePool::Singleton()->RequestDecode(mImage);
} else {
// Nothing more for us to do - let everyone know what happened.
NotifyProgressWorker::Dispatch(mImage);
}
return NS_OK;
}
protected:
virtual ~DecodeWorker()
{
if (gfxPrefs::ImageMTDecodingEnabled()) {
// Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
if (mainThread) {
// Handle ambiguous nsISupports inheritance
RasterImage* rawImg = nullptr;
mImage.swap(rawImg);
DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
}
}
}
private:
nsRefPtr<RasterImage> mImage;
};
#ifdef MOZ_NUWA_PROCESS
class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADPOOLLISTENER
RIDThreadPoolListener() { }
private:
~RIDThreadPoolListener() { }
};
NS_IMPL_ISUPPORTS(RIDThreadPoolListener, nsIThreadPoolListener)
NS_IMETHODIMP
RIDThreadPoolListener::OnThreadCreated()
{
if (IsNuwaProcess()) {
NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
}
return NS_OK;
}
NS_IMETHODIMP
RIDThreadPoolListener::OnThreadShuttingDown()
{
return NS_OK;
}
#endif // MOZ_NUWA_PROCESS
///////////////////////////////////////////////////////////////////////////////
// DecodePool implementation.
///////////////////////////////////////////////////////////////////////////////
/* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
/* static */ DecodePool*
DecodePool::Singleton()
{
if (!sSingleton) {
MOZ_ASSERT(NS_IsMainThread());
sSingleton = new DecodePool();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
already_AddRefed<nsIEventTarget>
DecodePool::GetEventTarget()
{
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
return target.forget();
}
DecodePool::DecodePool()
: mThreadPoolMutex("Thread Pool")
{
if (gfxPrefs::ImageMTDecodingEnabled()) {
mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
if (mThreadPool) {
mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
uint32_t limit;
if (prefLimit <= 0) {
limit = max(PR_GetNumberOfProcessors(), 2) - 1;
} else {
limit = static_cast<uint32_t>(prefLimit);
}
mThreadPool->SetThreadLimit(limit);
mThreadPool->SetIdleThreadLimit(limit);
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess()) {
mThreadPool->SetListener(new RIDThreadPoolListener());
}
#endif
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
}
}
}
}
DecodePool::~DecodePool()
{
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
}
NS_IMETHODIMP
DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
{
MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
nsCOMPtr<nsIThreadPool> threadPool;
{
MutexAutoLock threadPoolLock(mThreadPoolMutex);
threadPool = mThreadPool;
mThreadPool = nullptr;
}
if (threadPool) {
threadPool->Shutdown();
}
return NS_OK;
}
void
DecodePool::RequestDecode(RasterImage* aImage)
{
MOZ_ASSERT(aImage->mDecoder);
aImage->mDecodingMonitor.AssertCurrentThreadIn();
// If we're currently waiting on a new frame for this image, we can't do any
// decoding.
if (!aImage->mDecoder->NeedsNewFrame()) {
if (aImage->mDecodeStatus == DecodeStatus::PENDING ||
aImage->mDecodeStatus == DecodeStatus::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;
}
aImage->mDecodeStatus = DecodeStatus::PENDING;
nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage);
MutexAutoLock threadPoolLock(mThreadPoolMutex);
if (!gfxPrefs::ImageMTDecodingEnabled() || !mThreadPool) {
NS_DispatchToMainThread(worker);
} else {
mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
}
}
}
void
DecodePool::DecodeABitOf(RasterImage* aImage, DecodeStrategy aStrategy)
{
MOZ_ASSERT(NS_IsMainThread());
aImage->mDecodingMonitor.AssertCurrentThreadIn();
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
aImage->FinishedSomeDecoding();
}
DecodeSomeOfImage(aImage, aStrategy);
aImage->FinishedSomeDecoding();
// 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 (aImage->mDecoder && aImage->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::Dispatch(aImage);
} else {
// If we aren't yet finished decoding and we have more data in hand, add
// this request to the back of the priority list.
if (aImage->mDecoder &&
!aImage->mError &&
!aImage->IsDecodeFinished() &&
aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) {
RequestDecode(aImage);
}
}
}
/* static */ void
DecodePool::StopDecoding(RasterImage* aImage)
{
aImage->mDecodingMonitor.AssertCurrentThreadIn();
// If we haven't got a decode request, we're not currently decoding. (Having
// a decode request doesn't imply we *are* decoding, though.)
aImage->mDecodeStatus = DecodeStatus::STOPPED;
}
nsresult
DecodePool::DecodeUntilSizeAvailable(RasterImage* aImage)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter lock(aImage->mDecodingMonitor);
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
nsresult rv = aImage->FinishedSomeDecoding();
if (NS_FAILED(rv)) {
aImage->DoError();
return rv;
}
}
// We use DecodeStrategy::ASYNC here because we just want to get the size
// information here and defer the rest of the work.
nsresult rv =
DecodeSomeOfImage(aImage, DecodeStrategy::ASYNC, DecodeUntil::SIZE);
if (NS_FAILED(rv)) {
return rv;
}
// 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 (aImage->mDecoder && aImage->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::Dispatch(aImage);
} else {
rv = aImage->FinishedSomeDecoding();
}
return rv;
}
nsresult
DecodePool::DecodeSomeOfImage(RasterImage* aImage,
DecodeStrategy aStrategy,
DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */,
uint32_t bytesToDecode /* = 0 */)
{
MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container");
aImage->mDecodingMonitor.AssertCurrentThreadIn();
// If an error is flagged, it probably happened while we were waiting
// in the event queue.
if (aImage->mError) {
return NS_OK;
}
// If there is an error worker pending (say because the main thread has enqueued
// another decode request for us before processing the error worker) then bail out.
if (aImage->mPendingError) {
return NS_OK;
}
// If mDecoded or we don't have a decoder, we must have finished already (for
// example, a synchronous decode request came while the worker was pending).
if (!aImage->mDecoder || aImage->mDecoded) {
return NS_OK;
}
if (aImage->mDecoder->NeedsNewFrame()) {
if (aStrategy == DecodeStrategy::SYNC) {
MOZ_ASSERT(NS_IsMainThread());
aImage->mDecoder->AllocateFrame();
} else {
return NS_OK;
}
}
nsRefPtr<Decoder> decoderKungFuDeathGrip = aImage->mDecoder;
uint32_t maxBytes;
if (aImage->mDecoder->IsSizeDecode()) {
// Decode all available data if we're a size decode; they're cheap, and we
// want them to be more or less synchronous.
maxBytes = aImage->mSourceData.Length();
} else {
// We're only guaranteed to decode this many bytes, so in particular,
// gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us
// to read the size from most images.
maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime();
}
if (bytesToDecode == 0) {
bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded();
}
TimeStamp deadline = TimeStamp::Now() +
TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
// We keep decoding chunks until:
// * we don't have any data left to decode,
// * the decode completes,
// * we're an DecodeUntil::SIZE decode and we get the size, or
// * we run out of time.
// We also try to decode at least one "chunk" if we've allocated a new frame,
// even if we have no more data to send to the decoder.
while ((aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() &&
bytesToDecode > 0 &&
!aImage->IsDecodeFinished() &&
!(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize) &&
!aImage->mDecoder->NeedsNewFrame()) ||
aImage->mDecoder->NeedsToFlushData()) {
uint32_t chunkSize = min(bytesToDecode, maxBytes);
nsresult rv = aImage->DecodeSomeData(chunkSize, aStrategy);
if (NS_FAILED(rv)) {
aImage->DoError();
return rv;
}
bytesToDecode -= chunkSize;
// 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.
if (aDecodeUntil == DecodeUntil::TIME && TimeStamp::Now() >= deadline) {
break;
}
}
return NS_OK;
}
} // namespace image
} // namespace mozilla

151
image/src/DecodePool.h Normal file
View File

@ -0,0 +1,151 @@
/* -*- 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/. */
/**
* DecodePool manages the threads used for decoding raster images.
*/
#ifndef MOZILLA_IMAGELIB_DECODEPOOL_H_
#define MOZILLA_IMAGELIB_DECODEPOOL_H_
#include "mozilla/Mutex.h"
#include "mozilla/StaticPtr.h"
#include <mozilla/TypedEnum.h>
#include "nsCOMPtr.h"
#include "nsIEventTarget.h"
#include "nsIObserver.h"
class nsIThreadPool;
namespace mozilla {
namespace image {
class Decoder;
class RasterImage;
MOZ_BEGIN_ENUM_CLASS(DecodeStrategy, uint8_t)
// DecodeStrategy::SYNC requests a synchronous decode, which will continue
// decoding frames as long as it has more source data. It returns to the
// caller only once decoding is complete (or until it needs more source data
// before continuing). Because DecodeStrategy::SYNC can involve allocating new
// imgFrames, it can only be run on the main thread.
SYNC,
// DecodeStrategy::ASYNC requests an asynchronous decode, which will continue
// decoding until it either finishes a frame or runs out of source data.
// Because DecodeStrategy::ASYNC does not allocate new imgFrames, it can be
// safely run off the main thread. (And hence workers in the decode pool
// always use it.)
ASYNC
MOZ_END_ENUM_CLASS(DecodeStrategy)
MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
INACTIVE,
PENDING,
ACTIVE,
WORK_DONE,
STOPPED
MOZ_END_ENUM_CLASS(DecodeStatus)
MOZ_BEGIN_ENUM_CLASS(DecodeUntil, uint8_t)
TIME,
SIZE,
DONE_BYTES
MOZ_END_ENUM_CLASS(DecodeUntil)
MOZ_BEGIN_ENUM_CLASS(ShutdownReason, uint8_t)
DONE,
NOT_NEEDED,
FATAL_ERROR
MOZ_END_ENUM_CLASS(ShutdownReason)
/**
* DecodePool is a singleton class we use when decoding large images.
*
* When we wish to decode an image larger than
* 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
* the DecodePool singleton to the event queue, if it's not already pending
* there.
*
* 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
* the event loop.
*/
class DecodePool : public nsIObserver
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static DecodePool* Singleton();
/**
* Ask the DecodePool to asynchronously decode this image.
*/
void RequestDecode(RasterImage* aImage);
/**
* Decode aImage for a short amount of time, and post the remainder to the
* queue.
*/
void DecodeABitOf(RasterImage* aImage, DecodeStrategy aStrategy);
/**
* Ask the DecodePool to stop decoding this image. Internally, we also
* call this function when we finish decoding an image.
*
* Since the DecodePool keeps raw pointers to RasterImages, make sure you
* call this before a RasterImage is destroyed!
*/
static void StopDecoding(RasterImage* aImage);
/**
* Synchronously decode the beginning of the image until we run out of
* bytes or we get the image's size. Note that this done on a best-effort
* basis; if the size is burried too deep in the image, we'll give up.
*
* @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note
* that we return NS_OK even when the size was not found.)
*/
nsresult DecodeUntilSizeAvailable(RasterImage* aImage);
/**
* Returns an event target interface to the thread pool; primarily for
* OnDataAvailable delivery off main thread.
*
* @return An nsIEventTarget interface to mThreadPool.
*/
already_AddRefed<nsIEventTarget> GetEventTarget();
/**
* Decode some chunks of the given image. If aDecodeUntil is SIZE,
* decode until we have the image's size, then stop. If bytesToDecode is
* non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is
* DONE_BYTES, decode until all bytesToDecode bytes are decoded.
*/
nsresult DecodeSomeOfImage(RasterImage* aImage,
DecodeStrategy aStrategy,
DecodeUntil aDecodeUntil = DecodeUntil::TIME,
uint32_t bytesToDecode = 0);
private:
DecodePool();
virtual ~DecodePool();
static StaticRefPtr<DecodePool> sSingleton;
// mThreadPoolMutex protects mThreadPool. For all RasterImages R,
// R::mDecodingMonitor must be acquired before mThreadPoolMutex
// if both are acquired; the other order may cause deadlock.
Mutex mThreadPoolMutex;
nsCOMPtr<nsIThreadPool> mThreadPool;
};
} // namespace image
} // namespace mozilla
#endif // MOZILLA_IMAGELIB_DECODEPOOL_H_

View File

@ -1,36 +0,0 @@
/* -*- 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/. */
/** @file
* An enumeration used by RasterImage and Decoder to specify which 'strategy' to
* use for image decoding - synchronous or asynchronous.
*/
#ifndef mozilla_imagelib_DecodeStrategy_h_
#define mozilla_imagelib_DecodeStrategy_h_
namespace mozilla {
namespace image {
enum DecodeStrategy {
// DECODE_SYNC requests a synchronous decode, which will continue decoding
// frames as long as it has more source data. It returns to the caller only
// once decoding is complete (or until it needs more source data before
// continuing). Because DECODE_SYNC can involve allocating new imgFrames, it
// can only be run on the main thread.
DECODE_SYNC,
// DECODE_ASYNC requests an asynchronous decode, which will continue decoding
// until it either finishes a frame or runs out of source data. Because
// DECODE_ASYNC does not allocate new imgFrames, it can be safely run off the
// main thread. (And hence workers in the decode pool always use it.)
DECODE_ASYNC
};
} // namespace image
} // namespace mozilla
#endif

View File

@ -96,7 +96,7 @@ Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
PROFILER_LABEL("ImageDecoder", "Write", PROFILER_LABEL("ImageDecoder", "Write",
js::ProfileEntry::Category::GRAPHICS); js::ProfileEntry::Category::GRAPHICS);
MOZ_ASSERT(NS_IsMainThread() || aStrategy == DECODE_ASYNC); MOZ_ASSERT(NS_IsMainThread() || aStrategy == DecodeStrategy::ASYNC);
// We're strict about decoder errors // We're strict about decoder errors
MOZ_ASSERT(!HasDecoderError(), MOZ_ASSERT(!HasDecoderError(),
@ -129,12 +129,14 @@ Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
// If we're a synchronous decoder and we need a new frame to proceed, let's // If we're a synchronous decoder and we need a new frame to proceed, let's
// create one and call it again. // create one and call it again.
while (aStrategy == DECODE_SYNC && NeedsNewFrame() && !HasDataError()) { if (aStrategy == DecodeStrategy::SYNC) {
nsresult rv = AllocateFrame(); while (NeedsNewFrame() && !HasDataError()) {
nsresult rv = AllocateFrame();
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
// Tell the decoder to use the data it saved when it asked for a new frame. // Use the data we saved when we asked for a new frame.
WriteInternal(nullptr, 0, aStrategy); WriteInternal(nullptr, 0, aStrategy);
}
} }
} }
@ -143,7 +145,7 @@ Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
} }
void void
Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent) Decoder::Finish(ShutdownReason aReason)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -180,7 +182,7 @@ Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent)
} }
bool usable = !HasDecoderError(); bool usable = !HasDecoderError();
if (aShutdownIntent != RasterImage::eShutdownIntent_NotNeeded && !HasDecoderError()) { if (aReason != ShutdownReason::NOT_NEEDED && !HasDecoderError()) {
// If we only have a data error, we're usable if we have at least one complete frame. // If we only have a data error, we're usable if we have at least one complete frame.
if (GetCompleteFrameCount() == 0) { if (GetCompleteFrameCount() == 0) {
usable = false; usable = false;

View File

@ -8,7 +8,7 @@
#include "RasterImage.h" #include "RasterImage.h"
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
#include "DecodeStrategy.h" #include "DecodePool.h"
#include "ImageMetadata.h" #include "ImageMetadata.h"
#include "Orientation.h" #include "Orientation.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
@ -62,7 +62,7 @@ public:
* *
* Notifications Sent: TODO * Notifications Sent: TODO
*/ */
void Finish(RasterImage::eShutdownIntent aShutdownIntent); void Finish(ShutdownReason aReason);
/** /**
* Informs the shared decoder that all the data has been written. * Informs the shared decoder that all the data has been written.

View File

@ -20,9 +20,6 @@
#include "ImageRegion.h" #include "ImageRegion.h"
#include "Layers.h" #include "Layers.h"
#include "nsPresContext.h" #include "nsPresContext.h"
#include "nsIThreadPool.h"
#include "nsXPCOMCIDInternal.h"
#include "nsIObserverService.h"
#include "SurfaceCache.h" #include "SurfaceCache.h"
#include "FrameAnimator.h" #include "FrameAnimator.h"
@ -51,10 +48,6 @@
#include "gfxPrefs.h" #include "gfxPrefs.h"
#include <algorithm> #include <algorithm>
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#endif
namespace mozilla { namespace mozilla {
using namespace gfx; using namespace gfx;
@ -296,7 +289,6 @@ private:
ScaleState mState; ScaleState mState;
}; };
/* static */ StaticRefPtr<RasterImage::DecodePool> RasterImage::DecodePool::sSingleton;
static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr; static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
#ifndef DEBUG #ifndef DEBUG
@ -1545,7 +1537,7 @@ RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
// write the data directly to the decoder. (If we haven't gotten the size, // write the data directly to the decoder. (If we haven't gotten the size,
// we'll queue up the data and write it out when we do.) // we'll queue up the data and write it out when we do.)
if (!StoringSourceData() && mHasSize) { if (!StoringSourceData() && mHasSize) {
rv = WriteToDecoder(aBuffer, aCount, DECODE_SYNC); rv = WriteToDecoder(aBuffer, aCount, DecodeStrategy::SYNC);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
rv = FinishedSomeDecoding(); rv = FinishedSomeDecoding();
@ -1665,7 +1657,7 @@ 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.
{ {
ReentrantMonitorAutoEnter lock(mDecodingMonitor); ReentrantMonitorAutoEnter lock(mDecodingMonitor);
FinishedSomeDecoding(eShutdownIntent_Done, FinishedSomeDecoding(ShutdownReason::DONE,
LoadCompleteProgress(aLastPart, mError, finalStatus)); LoadCompleteProgress(aLastPart, mError, finalStatus));
} }
@ -1745,6 +1737,12 @@ RasterImage::OnNewSourceData()
return NS_OK; return NS_OK;
} }
/* static */ already_AddRefed<nsIEventTarget>
RasterImage::GetEventTarget()
{
return DecodePool::Singleton()->GetEventTarget();
}
nsresult nsresult
RasterImage::SetSourceSizeHint(uint32_t sizeHint) RasterImage::SetSourceSizeHint(uint32_t sizeHint)
{ {
@ -1989,20 +1987,17 @@ RasterImage::InitDecoder(bool aDoSizeDecode)
// state. It is an error to call this function when there is no initialized // state. It is an error to call this function when there is no initialized
// decoder. // decoder.
// //
// aIntent specifies the intent of the shutdown. If aIntent is // aReason specifies why the shutdown is happening. If aReason is
// eShutdownIntent_Done, an error is flagged if we didn't get what we should // ShutdownReason::DONE, an error is flagged if we didn't get what we should
// have out of the decode. If aIntent is eShutdownIntent_NotNeeded, we don't // have out of the decode. If aReason is ShutdownReason::NOT_NEEDED, we don't
// check this. If aIntent is eShutdownIntent_Error, we shut down in error mode. // check this. If aReason is ShutdownReason::FATAL_ERROR, we shut down in error
// mode.
nsresult nsresult
RasterImage::ShutdownDecoder(eShutdownIntent aIntent) RasterImage::ShutdownDecoder(ShutdownReason aReason)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
mDecodingMonitor.AssertCurrentThreadIn(); mDecodingMonitor.AssertCurrentThreadIn();
// Ensure that our intent is valid
NS_ABORT_IF_FALSE((aIntent >= 0) && (aIntent < eShutdownIntent_AllCount),
"Invalid shutdown intent");
// Ensure that the decoder is initialized // Ensure that the decoder is initialized
NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!"); NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!");
@ -2015,7 +2010,7 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
nsRefPtr<Decoder> decoder = mDecoder; nsRefPtr<Decoder> decoder = mDecoder;
mDecoder = nullptr; mDecoder = nullptr;
decoder->Finish(aIntent); decoder->Finish(aReason);
// 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
// have a decoder open, the last frame is always locked. // have a decoder open, the last frame is always locked.
@ -2036,12 +2031,8 @@ RasterImage::ShutdownDecoder(eShutdownIntent aIntent)
// We just shut down the decoder. If we didn't get what we want, but expected // We just shut down the decoder. If we didn't get what we want, but expected
// to, flag an error // to, flag an error
bool failed = false; bool succeeded = wasSizeDecode ? mHasSize : mDecoded;
if (wasSizeDecode && !mHasSize) if ((aReason == ShutdownReason::DONE) && !succeeded) {
failed = true;
if (!wasSizeDecode && !mDecoded)
failed = true;
if ((aIntent == eShutdownIntent_Done) && failed) {
DoError(); DoError();
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
@ -2223,7 +2214,7 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
// 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) {
nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded); nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
} }
@ -2250,7 +2241,7 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf",
js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get()); js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
DecodePool::Singleton()->DecodeABitOf(this, DECODE_SYNC); DecodePool::Singleton()->DecodeABitOf(this, DecodeStrategy::SYNC);
return NS_OK; return NS_OK;
} }
@ -2309,7 +2300,7 @@ RasterImage::SyncDecode()
// If we have a decoder open with different flags than what we need, shut it // If we have a decoder open with different flags than what we need, shut it
// down // down
if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) { if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded); nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
if (mDecoded) { if (mDecoded) {
@ -2337,7 +2328,7 @@ RasterImage::SyncDecode()
// Write everything we have // Write everything we have
rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded(), rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded(),
DECODE_SYNC); DecodeStrategy::SYNC);
CONTAINER_ENSURE_SUCCESS(rv); CONTAINER_ENSURE_SUCCESS(rv);
rv = FinishedSomeDecoding(); rv = FinishedSomeDecoding();
@ -2652,7 +2643,7 @@ RasterImage::UnlockImage()
("RasterImage[0x%p] canceling decode because image " ("RasterImage[0x%p] canceling decode because image "
"is now unlocked.", this)); "is now unlocked.", this));
ReentrantMonitorAutoEnter lock(mDecodingMonitor); ReentrantMonitorAutoEnter lock(mDecodingMonitor);
FinishedSomeDecoding(eShutdownIntent_NotNeeded); FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
ForceDiscard(); ForceDiscard();
return NS_OK; return NS_OK;
} }
@ -2773,7 +2764,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) {
FinishedSomeDecoding(eShutdownIntent_Error); FinishedSomeDecoding(ShutdownReason::FATAL_ERROR);
} }
// Put the container in an error state. // Put the container in an error state.
@ -2859,7 +2850,7 @@ RasterImage::GetFramesNotified(uint32_t *aFramesNotified)
nsresult nsresult
RasterImage::RequestDecodeIfNeeded(nsresult aStatus, RasterImage::RequestDecodeIfNeeded(nsresult aStatus,
eShutdownIntent aIntent, ShutdownReason aReason,
bool aDone, bool aDone,
bool aWasSize) bool aWasSize)
{ {
@ -2867,7 +2858,7 @@ RasterImage::RequestDecodeIfNeeded(nsresult aStatus,
// If we were a size decode and a full decode was requested, now's the time. // If we were a size decode and a full decode was requested, now's the time.
if (NS_SUCCEEDED(aStatus) && if (NS_SUCCEEDED(aStatus) &&
aIntent == eShutdownIntent_Done && aReason == ShutdownReason::DONE &&
aDone && aDone &&
aWasSize && aWasSize &&
mWantFullDecode) { mWantFullDecode) {
@ -2885,7 +2876,7 @@ RasterImage::RequestDecodeIfNeeded(nsresult aStatus,
} }
nsresult nsresult
RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_Done */, RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DONE */,
Progress aProgress /* = NoProgress */) Progress aProgress /* = NoProgress */)
{ {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
@ -2917,7 +2908,7 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
// If the decode finished, or we're specifically being told to shut down, // If the decode finished, or we're specifically being told to shut down,
// tell the image and shut down the decoder. // tell the image and shut down the decoder.
if (image->IsDecodeFinished() || aIntent != eShutdownIntent_Done) { if (image->IsDecodeFinished() || aReason != ShutdownReason::DONE) {
done = true; done = true;
// Hold on to a reference to the decoder until we're done with it // Hold on to a reference to the decoder until we're done with it
@ -2942,7 +2933,7 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
// We need to shut down the decoder first, in order to ensure all // We need to shut down the decoder first, in order to ensure all
// decoding routines have been finished. // decoding routines have been finished.
rv = image->ShutdownDecoder(aIntent); rv = image->ShutdownDecoder(aReason);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
image->DoError(); image->DoError();
} }
@ -2994,7 +2985,7 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
} }
} }
return RequestDecodeIfNeeded(rv, aIntent, done, wasSize); return RequestDecodeIfNeeded(rv, aReason, done, wasSize);
} }
already_AddRefed<imgIContainer> already_AddRefed<imgIContainer>
@ -3004,447 +2995,6 @@ RasterImage::Unwrap()
return self.forget(); return self.forget();
} }
NS_IMPL_ISUPPORTS(RasterImage::DecodePool,
nsIObserver)
/* static */ RasterImage::DecodePool*
RasterImage::DecodePool::Singleton()
{
if (!sSingleton) {
MOZ_ASSERT(NS_IsMainThread());
sSingleton = new DecodePool();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
already_AddRefed<nsIEventTarget>
RasterImage::DecodePool::GetEventTarget()
{
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
return target.forget();
}
#ifdef MOZ_NUWA_PROCESS
class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADPOOLLISTENER
RIDThreadPoolListener() {}
~RIDThreadPoolListener() {}
};
NS_IMPL_ISUPPORTS(RIDThreadPoolListener, nsIThreadPoolListener)
NS_IMETHODIMP
RIDThreadPoolListener::OnThreadCreated()
{
if (IsNuwaProcess()) {
NuwaMarkCurrentThread((void (*)(void *))nullptr, nullptr);
}
return NS_OK;
}
NS_IMETHODIMP
RIDThreadPoolListener::OnThreadShuttingDown()
{
return NS_OK;
}
#endif // MOZ_NUWA_PROCESS
RasterImage::DecodePool::DecodePool()
: mThreadPoolMutex("Thread Pool")
{
if (gfxPrefs::ImageMTDecodingEnabled()) {
mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
if (mThreadPool) {
mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
uint32_t limit;
if (prefLimit <= 0) {
limit = std::max(PR_GetNumberOfProcessors(), 2) - 1;
} else {
limit = static_cast<uint32_t>(prefLimit);
}
mThreadPool->SetThreadLimit(limit);
mThreadPool->SetIdleThreadLimit(limit);
#ifdef MOZ_NUWA_PROCESS
if (IsNuwaProcess()) {
mThreadPool->SetListener(new RIDThreadPoolListener());
}
#endif
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
}
}
}
}
RasterImage::DecodePool::~DecodePool()
{
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
}
NS_IMETHODIMP
RasterImage::DecodePool::Observe(nsISupports *subject, const char *topic,
const char16_t *data)
{
NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
nsCOMPtr<nsIThreadPool> threadPool;
{
MutexAutoLock threadPoolLock(mThreadPoolMutex);
threadPool = mThreadPool;
mThreadPool = nullptr;
}
if (threadPool) {
threadPool->Shutdown();
}
return NS_OK;
}
void
RasterImage::DecodePool::RequestDecode(RasterImage* aImg)
{
MOZ_ASSERT(aImg->mDecoder);
aImg->mDecodingMonitor.AssertCurrentThreadIn();
// If we're currently waiting on a new frame for this image, we can't do any
// decoding.
if (!aImg->mDecoder->NeedsNewFrame()) {
if (aImg->mDecodeStatus == DecodeStatus::PENDING ||
aImg->mDecodeStatus == DecodeStatus::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->mDecodeStatus = DecodeStatus::PENDING;
nsRefPtr<DecodeJob> job = new DecodeJob(aImg);
MutexAutoLock threadPoolLock(mThreadPoolMutex);
if (!gfxPrefs::ImageMTDecodingEnabled() || !mThreadPool) {
NS_DispatchToMainThread(job);
} else {
mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
}
}
}
void
RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy)
{
MOZ_ASSERT(NS_IsMainThread());
aImg->mDecodingMonitor.AssertCurrentThreadIn();
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImg->mDecodeStatus == DecodeStatus::WORK_DONE) {
aImg->FinishedSomeDecoding();
}
DecodeSomeOfImage(aImg, aStrategy);
aImg->FinishedSomeDecoding();
// 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 (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::GetNewFrame(aImg);
} else {
// If we aren't yet finished decoding and we have more data in hand, add
// this request to the back of the priority list.
if (aImg->mDecoder &&
!aImg->mError &&
!aImg->IsDecodeFinished() &&
aImg->mSourceData.Length() > aImg->mDecoder->BytesDecoded()) {
RequestDecode(aImg);
}
}
}
/* static */ void
RasterImage::DecodePool::StopDecoding(RasterImage* aImg)
{
aImg->mDecodingMonitor.AssertCurrentThreadIn();
// If we haven't got a decode request, we're not currently decoding. (Having
// a decode request doesn't imply we *are* decoding, though.)
aImg->mDecodeStatus = DecodeStatus::STOPPED;
}
NS_IMETHODIMP
RasterImage::DecodePool::DecodeJob::Run()
{
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
// If we were interrupted, we shouldn't do any work.
if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage);
return NS_OK;
}
// If someone came along and synchronously decoded us, there's nothing for us to do.
if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage);
return NS_OK;
}
// If we're a decode job that's been enqueued since a previous decode that
// still needs a new frame, we can't do anything. Wait until the
// FrameNeededWorker enqueues another frame.
if (mImage->mDecoder->NeedsNewFrame()) {
return NS_OK;
}
mImage->mDecodeStatus = DecodeStatus::ACTIVE;
size_t oldByteCount = mImage->mDecoder->BytesDecoded();
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;
}
size_t maxBytes = mImage->mSourceData.Length() -
mImage->mDecoder->BytesDecoded();
DecodePool::Singleton()->DecodeSomeOfImage(mImage, DECODE_ASYNC,
type, maxBytes);
size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
mImage->mDecodeStatus = DecodeStatus::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->mPendingError &&
!mImage->IsDecodeFinished() &&
bytesDecoded < maxBytes &&
bytesDecoded > 0) {
DecodePool::Singleton()->RequestDecode(mImage);
} else {
// Nothing more for us to do - let everyone know what happened.
DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage);
}
return NS_OK;
}
RasterImage::DecodePool::DecodeJob::~DecodeJob()
{
if (gfxPrefs::ImageMTDecodingEnabled()) {
// Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
if (mainThread) {
// Handle ambiguous nsISupports inheritance
RasterImage* rawImg = nullptr;
mImage.swap(rawImg);
DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
}
}
}
nsresult
RasterImage::DecodePool::DecodeUntilSizeAvailable(RasterImage* aImg)
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter lock(aImg->mDecodingMonitor);
// If the image is waiting for decode work to be notified, go ahead and do that.
if (aImg->mDecodeStatus == DecodeStatus::WORK_DONE) {
nsresult rv = aImg->FinishedSomeDecoding();
if (NS_FAILED(rv)) {
aImg->DoError();
return rv;
}
}
// We use DECODE_ASYNC here because we just want to get the size information
// here and defer the rest of the work.
nsresult rv = DecodeSomeOfImage(aImg, DECODE_ASYNC, DECODE_TYPE_UNTIL_SIZE);
if (NS_FAILED(rv)) {
return rv;
}
// 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 (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
FrameNeededWorker::GetNewFrame(aImg);
} else {
rv = aImg->FinishedSomeDecoding();
}
return rv;
}
nsresult
RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
DecodeStrategy aStrategy,
DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */,
uint32_t bytesToDecode /* = 0 */)
{
NS_ABORT_IF_FALSE(aImg->mInitialized,
"Worker active for uninitialized container!");
aImg->mDecodingMonitor.AssertCurrentThreadIn();
// If an error is flagged, it probably happened while we were waiting
// in the event queue.
if (aImg->mError)
return NS_OK;
// If there is an error worker pending (say because the main thread has enqueued
// another decode request for us before processing the error worker) then bail out.
if (aImg->mPendingError)
return NS_OK;
// If mDecoded or we don't have a decoder, we must have finished already (for
// example, a synchronous decode request came while the worker was pending).
if (!aImg->mDecoder || aImg->mDecoded)
return NS_OK;
// If we're doing synchronous decodes, and we're waiting on a new frame for
// this image, get it now.
if (aStrategy == DECODE_SYNC && aImg->mDecoder->NeedsNewFrame()) {
MOZ_ASSERT(NS_IsMainThread());
aImg->mDecoder->AllocateFrame();
}
// If we're not synchronous, we can't allocate a frame right now.
else if (aImg->mDecoder->NeedsNewFrame()) {
return NS_OK;
}
nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
uint32_t maxBytes;
if (aImg->mDecoder->IsSizeDecode()) {
// Decode all available data if we're a size decode; they're cheap, and we
// want them to be more or less synchronous.
maxBytes = aImg->mSourceData.Length();
} else {
// We're only guaranteed to decode this many bytes, so in particular,
// gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us
// to read the size from most images.
maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime();
}
if (bytesToDecode == 0) {
bytesToDecode = aImg->mSourceData.Length() - aImg->mDecoder->BytesDecoded();
}
TimeStamp deadline = TimeStamp::Now() +
TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
// We keep decoding chunks until:
// * we don't have any data left to decode,
// * the decode completes,
// * we're an UNTIL_SIZE decode and we get the size, or
// * we run out of time.
// We also try to decode at least one "chunk" if we've allocated a new frame,
// even if we have no more data to send to the decoder.
while ((aImg->mSourceData.Length() > aImg->mDecoder->BytesDecoded() &&
bytesToDecode > 0 &&
!aImg->IsDecodeFinished() &&
!(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize) &&
!aImg->mDecoder->NeedsNewFrame()) ||
aImg->mDecoder->NeedsToFlushData()) {
uint32_t chunkSize = std::min(bytesToDecode, maxBytes);
nsresult rv = aImg->DecodeSomeData(chunkSize, aStrategy);
if (NS_FAILED(rv)) {
aImg->DoError();
return rv;
}
bytesToDecode -= chunkSize;
// 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.
if (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline)
break;
}
return NS_OK;
}
RasterImage::DecodeDoneWorker::DecodeDoneWorker(RasterImage* aImage)
: mImage(aImage)
{ }
void
RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* aImage)
{
aImage->mDecodingMonitor.AssertCurrentThreadIn();
nsCOMPtr<nsIRunnable> worker = new DecodeDoneWorker(aImage);
NS_DispatchToMainThread(worker);
}
NS_IMETHODIMP
RasterImage::DecodeDoneWorker::Run()
{
MOZ_ASSERT(NS_IsMainThread());
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
mImage->FinishedSomeDecoding(eShutdownIntent_Done);
return NS_OK;
}
RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image)
: mImage(image)
{}
void
RasterImage::FrameNeededWorker::GetNewFrame(RasterImage* image)
{
nsCOMPtr<nsIRunnable> worker = new FrameNeededWorker(image);
NS_DispatchToMainThread(worker);
}
NS_IMETHODIMP
RasterImage::FrameNeededWorker::Run()
{
ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
nsresult rv = NS_OK;
// If we got a synchronous decode in the mean time, we don't need to do
// anything.
if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
rv = mImage->mDecoder->AllocateFrame();
}
if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
// By definition, we're not done decoding, so enqueue us for more decoding.
DecodePool::Singleton()->RequestDecode(mImage);
}
return NS_OK;
}
nsIntSize nsIntSize
RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame,
GraphicsFilter aFilter, uint32_t aFlags) GraphicsFilter aFilter, uint32_t aFlags)

View File

@ -25,16 +25,14 @@
#include "nsTArray.h" #include "nsTArray.h"
#include "imgFrame.h" #include "imgFrame.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "DecodeStrategy.h" #include "DecodePool.h"
#include "DiscardTracker.h" #include "DiscardTracker.h"
#include "Orientation.h" #include "Orientation.h"
#include "nsIObserver.h" #include "nsIObserver.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/ReentrantMonitor.h" #include "mozilla/ReentrantMonitor.h"
#include "mozilla/TimeStamp.h" #include "mozilla/TimeStamp.h"
#include "mozilla/TypedEnum.h" #include "mozilla/TypedEnum.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
#ifdef DEBUG #ifdef DEBUG
@ -135,15 +133,6 @@ namespace image {
class Decoder; class Decoder;
class FrameAnimator; class FrameAnimator;
class ScaleRunner;
MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
INACTIVE,
PENDING,
ACTIVE,
WORK_DONE,
STOPPED
MOZ_END_ENUM_CLASS(DecodeStatus)
class RasterImage MOZ_FINAL : public ImageResource class RasterImage MOZ_FINAL : public ImageResource
, public nsIProperties , public nsIProperties
@ -260,9 +249,7 @@ public:
bool aLastPart) MOZ_OVERRIDE; bool aLastPart) MOZ_OVERRIDE;
virtual nsresult OnNewSourceData() MOZ_OVERRIDE; virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
static already_AddRefed<nsIEventTarget> GetEventTarget() { static already_AddRefed<nsIEventTarget> GetEventTarget();
return DecodePool::Singleton()->GetEventTarget();
}
/** /**
* A hint of the number of bytes of source data that the image contains. If * A hint of the number of bytes of source data that the image contains. If
@ -294,8 +281,6 @@ public:
return mRequestedSampleSize; return mRequestedSampleSize;
} }
nsCString GetURIString() { nsCString GetURIString() {
nsCString spec; nsCString spec;
if (GetURI()) { if (GetURI()) {
@ -304,168 +289,15 @@ public:
return spec; return spec;
} }
// Called from module startup. Sets up RasterImage to be used.
static void Initialize(); static void Initialize();
// Decoder shutdown
enum eShutdownIntent {
eShutdownIntent_Done = 0,
eShutdownIntent_NotNeeded = 1,
eShutdownIntent_Error = 2,
eShutdownIntent_AllCount = 3
};
private: private:
/* friend class DecodePool;
* DecodePool is a singleton class we use when decoding large images. friend class DecodeWorker;
* friend class FrameNeededWorker;
* When we wish to decode an image larger than friend class NotifyProgressWorker;
* 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
* the DecodePool singleton to the event queue, if it's not already pending
* there.
*
* 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
* the event loop.
*/
class DecodePool : public nsIObserver
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static DecodePool* Singleton(); nsresult FinishedSomeDecoding(ShutdownReason aReason = ShutdownReason::DONE,
/**
* Ask the DecodePool to asynchronously decode this image.
*/
void RequestDecode(RasterImage* aImg);
/**
* Decode aImg for a short amount of time, and post the remainder to the
* queue.
*/
void DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy);
/**
* Ask the DecodePool to stop decoding this image. Internally, we also
* call this function when we finish decoding an image.
*
* Since the DecodePool keeps raw pointers to RasterImages, make sure you
* call this before a RasterImage is destroyed!
*/
static void StopDecoding(RasterImage* aImg);
/**
* Synchronously decode the beginning of the image until we run out of
* bytes or we get the image's size. Note that this done on a best-effort
* basis; if the size is burried too deep in the image, we'll give up.
*
* @return NS_ERROR if an error is encountered, and NS_OK otherwise. (Note
* that we return NS_OK even when the size was not found.)
*/
nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
/**
* Returns an event target interface to the thread pool; primarily for
* OnDataAvailable delivery off main thread.
*
* @return An nsIEventTarget interface to mThreadPool.
*/
already_AddRefed<nsIEventTarget> GetEventTarget();
private: /* statics */
static StaticRefPtr<DecodePool> sSingleton;
private: /* methods */
DecodePool();
virtual ~DecodePool();
enum DecodeType {
DECODE_TYPE_UNTIL_TIME,
DECODE_TYPE_UNTIL_SIZE,
DECODE_TYPE_UNTIL_DONE_BYTES
};
/* 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
* 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,
DecodeStrategy aStrategy,
DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
uint32_t bytesToDecode = 0);
/* A decode job dispatched to a thread pool by DecodePool.
*/
class DecodeJob : public nsRunnable
{
public:
DecodeJob(RasterImage* aImage) : mImage(aImage) { }
NS_IMETHOD Run() MOZ_OVERRIDE;
protected:
virtual ~DecodeJob();
private:
nsRefPtr<RasterImage> mImage;
};
private: /* members */
// mThreadPoolMutex protects mThreadPool. For all RasterImages R,
// R::mDecodingMonitor must be acquired before mThreadPoolMutex
// if both are acquired; the other order may cause deadlock.
Mutex mThreadPoolMutex;
nsCOMPtr<nsIThreadPool> mThreadPool;
};
class DecodeDoneWorker : public nsRunnable
{
public:
/**
* Called by the DecodePool with an image when it's done some significant
* portion of decoding that needs to be notified about.
*
* Ensures the decode state accumulated by the decoding process gets
* applied to the image.
*/
static void NotifyFinishedSomeDecoding(RasterImage* aImage);
NS_IMETHOD Run();
private:
DecodeDoneWorker(RasterImage* aImage);
nsRefPtr<RasterImage> mImage;
};
class FrameNeededWorker : public nsRunnable
{
public:
/**
* 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.
*
* Dispatches an event to do so, which will further dispatch a
* RequestDecode event to continue decoding.
*/
static void GetNewFrame(RasterImage* image);
NS_IMETHOD Run();
private: /* methods */
explicit FrameNeededWorker(RasterImage* image);
private: /* members */
nsRefPtr<RasterImage> mImage;
};
nsresult FinishedSomeDecoding(eShutdownIntent intent = eShutdownIntent_Done,
Progress aProgress = NoProgress); Progress aProgress = NoProgress);
void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef, void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
@ -624,10 +456,8 @@ private: // data
bool mPendingError:1; bool mPendingError:1;
// Decoding // Decoding
nsresult RequestDecodeIfNeeded(nsresult aStatus, nsresult RequestDecodeIfNeeded(nsresult aStatus, ShutdownReason aReason,
eShutdownIntent aIntent, bool aDone, bool aWasSize);
bool aDone,
bool aWasSize);
nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify); nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify);
nsresult SyncDecode(); nsresult SyncDecode();
nsresult InitDecoder(bool aDoSizeDecode); nsresult InitDecoder(bool aDoSizeDecode);
@ -639,7 +469,7 @@ private: // data
// Initializes ProgressTracker and resets it on RasterImage destruction. // Initializes ProgressTracker and resets it on RasterImage destruction.
nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit; nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
nsresult ShutdownDecoder(eShutdownIntent aIntent); nsresult ShutdownDecoder(ShutdownReason aReason);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////

View File

@ -16,6 +16,7 @@ EXPORTS += [
UNIFIED_SOURCES += [ UNIFIED_SOURCES += [
'ClippedImage.cpp', 'ClippedImage.cpp',
'DecodePool.cpp',
'Decoder.cpp', 'Decoder.cpp',
'DiscardTracker.cpp', 'DiscardTracker.cpp',
'DynamicImage.cpp', 'DynamicImage.cpp',