Bug 1060200 (Part 3) - Store HQ scaled frames in SurfaceCache and remove ScaleRequest and ScaleResult. r=tn

This commit is contained in:
Seth Fowler 2014-09-14 21:51:20 -07:00
Родитель 23fbab674a
Коммит 8f869159cc
2 изменённых файлов: 218 добавлений и 308 удалений

Просмотреть файл

@ -24,6 +24,7 @@
#include "nsIThreadPool.h"
#include "nsXPCOMCIDInternal.h"
#include "nsIObserverService.h"
#include "SurfaceCache.h"
#include "FrameAnimator.h"
#include "nsPNGDecoder.h"
@ -67,6 +68,12 @@ using std::ceil;
#define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION)
#define DECODE_FLAGS_DEFAULT 0
static uint32_t
DecodeFlags(uint32_t aFlags)
{
return aFlags & DECODE_FLAGS_MASK;
}
/* Accounting for compressed data */
#if defined(PR_LOGGING)
static PRLogModuleInfo *
@ -184,184 +191,141 @@ DiscardingEnabled()
return enabled;
}
class ScaleRequest
class ScaleRunner : public nsRunnable
{
public:
ScaleRequest(RasterImage* aImage,
const nsIntSize& aSize,
RawAccessFrameRef&& aSrcRef)
: weakImage(aImage)
, srcRef(Move(aSrcRef))
, srcRect(srcRef->GetRect())
, dstSize(aSize)
, done(false)
, stopped(false)
enum ScaleState
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!srcRef->GetIsPaletted());
eNew,
eReady,
eFinish,
eFinishWithError
};
public:
ScaleRunner(RasterImage* aImage,
uint32_t aImageFlags,
const nsIntSize& aSize,
RawAccessFrameRef&& aSrcRef)
: mImage(aImage)
, mSrcRef(Move(aSrcRef))
, mDstSize(aSize)
, mImageFlags(aImageFlags)
, mState(eNew)
{
MOZ_ASSERT(!mSrcRef->GetIsPaletted());
MOZ_ASSERT(aSize.width > 0 && aSize.height > 0);
}
// This can only be called on the main thread.
bool AcquireResources()
bool Init()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == eNew, "Calling Init() twice?");
nsRefPtr<RasterImage> image = weakImage.get();
if (!image) {
// We'll need a destination frame. It's unconditionally ARGB32 because
// that's what the scaler outputs.
nsRefPtr<imgFrame> tentativeDstFrame = new imgFrame();
nsresult rv =
tentativeDstFrame->InitForDecoder(mDstSize, SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
return false;
}
if (!dstFrame) {
// We need to hold a lock onto the RasterImage object itself so that
// it (and its associated imgFrames) aren't marked as discardable.
if (NS_FAILED(image->LockImage())) {
return false;
}
// We'll need a destination frame. It's unconditionally ARGB32 because
// that's what the scaler outputs.
nsRefPtr<imgFrame> tentativeDstFrame = new imgFrame();
nsresult rv =
tentativeDstFrame->InitForDecoder(dstSize, SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
return false;
}
// We need a strong reference to the raw data for the destination frame.
// (We already got one for the source frame in the constructor.)
RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef();
if (!tentativeDstRef) {
return false;
}
dstFrame = tentativeDstFrame.forget();
dstRef = Move(tentativeDstRef);
// We need a strong reference to the raw data for the destination frame.
// (We already got one for the source frame in the constructor.)
RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef();
if (!tentativeDstRef) {
return false;
}
// Everything worked, so commit to these objects and mark ourselves ready.
mDstRef = Move(tentativeDstRef);
mState = eReady;
// Insert the new surface into the cache immediately. We need to do this so
// that we won't start multiple scaling jobs for the same size.
SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags));
return true;
}
// This can only be called on the main thread.
void ReleaseResources()
~ScaleRunner()
{
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<RasterImage> image = weakImage.get();
if (image) {
image->UnlockImage();
}
if (DiscardingEnabled() && dstFrame) {
dstFrame->SetDiscardable();
}
// Release everything except dstFrame, which we keep around for RasterImage
// to retrieve.
srcRef.reset();
dstRef.reset();
}
// These values may only be modified on the main thread.
WeakPtr<RasterImage> weakImage;
nsRefPtr<imgFrame> dstFrame;
RawAccessFrameRef srcRef;
RawAccessFrameRef dstRef;
// Below are the values that may be modified on the scaling thread.
nsIntRect srcRect;
nsIntSize dstSize;
bool done;
// This boolean is accessed from both threads simultaneously without locking.
// That's safe because stopping a ScaleRequest is strictly an optimization;
// if we're not cache-coherent, at worst we'll do extra work.
bool stopped;
};
class DrawRunner : public nsRunnable
{
public:
explicit DrawRunner(ScaleRequest* request)
: mScaleRequest(request)
{}
NS_IMETHOD Run()
{
// Grab the weak image pointer before the request releases it.
nsRefPtr<RasterImage> image = mScaleRequest->weakImage.get();
// ScaleWorker is finished with this request, so release everything that we
// don't need anymore.
mScaleRequest->ReleaseResources();
if (image) {
RasterImage::ScaleStatus status;
if (mScaleRequest->done) {
status = RasterImage::SCALE_DONE;
} else {
status = RasterImage::SCALE_INVALID;
}
image->ScalingDone(mScaleRequest, status);
}
return NS_OK;
}
private: /* members */
nsAutoPtr<ScaleRequest> mScaleRequest;
};
class ScaleRunner : public nsRunnable
{
public:
ScaleRunner(RasterImage* aImage,
const nsIntSize& aSize,
RawAccessFrameRef&& aSrcRef)
{
nsAutoPtr<ScaleRequest> req(new ScaleRequest(aImage, aSize, Move(aSrcRef)));
if (!req->AcquireResources()) {
return;
}
aImage->ScalingStart(req);
mScaleRequest = req;
MOZ_ASSERT(!mSrcRef && !mDstRef,
"Should have released strong refs in Run()");
}
NS_IMETHOD Run()
{
ScaleRequest* req = mScaleRequest.get();
if (!req->stopped) {
if (mState == eReady) {
// Collect information from the frames that we need to scale.
uint8_t* srcData = req->srcRef->GetImageData();
uint8_t* dstData = req->dstRef->GetImageData();
uint32_t srcStride = req->srcRef->GetImageBytesPerRow();
uint32_t dstStride = req->dstRef->GetImageBytesPerRow();
SurfaceFormat srcFormat = req->srcRef->GetFormat();
uint8_t* srcData = mSrcRef->GetImageData();
IntSize srcSize = mSrcRef->GetSize();
uint32_t srcStride = mSrcRef->GetImageBytesPerRow();
uint8_t* dstData = mDstRef->GetImageData();
uint32_t dstStride = mDstRef->GetImageBytesPerRow();
SurfaceFormat srcFormat = mSrcRef->GetFormat();
// Actually do the scaling.
req->done =
gfx::Scale(srcData, req->srcRect.width, req->srcRect.height, srcStride,
dstData, req->dstSize.width, req->dstSize.height, dstStride,
bool succeeded =
gfx::Scale(srcData, srcSize.width, srcSize.height, srcStride,
dstData, mDstSize.width, mDstSize.height, dstStride,
srcFormat);
} else {
req->done = false;
}
// OK, we've got a new scaled image. Let's get the main thread to unlock and
// redraw it.
nsRefPtr<DrawRunner> runner = new DrawRunner(mScaleRequest.forget());
NS_DispatchToMainThread(runner);
if (succeeded) {
// Mark the frame as complete and discardable.
mDstRef->ImageUpdated(mDstRef->GetRect());
MOZ_ASSERT(mDstRef->ImageComplete(),
"Incomplete, but just updated the entire frame");
if (DiscardingEnabled()) {
mDstRef->SetDiscardable();
}
}
// We need to send notifications and release our references on the main
// thread, so finish up there.
mState = succeeded ? eFinish : eFinishWithError;
NS_DispatchToMainThread(this);
} else if (mState == eFinish) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mDstRef, "Should have a valid scaled frame");
// Notify, so observers can redraw.
nsRefPtr<RasterImage> image = mImage.get();
if (image) {
image->NotifyNewScaledFrame();
}
// We're done, so release everything.
mSrcRef.reset();
mDstRef.reset();
} else if (mState == eFinishWithError) {
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("HQ scaling failed");
// Remove the frame from the cache since we know we don't need it.
SurfaceCache::RemoveIfPresent(ImageKey(mImage.get()),
RasterSurfaceKey(mDstSize.ToIntSize(),
mImageFlags));
// Release everything we're holding, too.
mSrcRef.reset();
mDstRef.reset();
} else {
// mState must be eNew, which is invalid in Run().
MOZ_ASSERT(false, "Need to call Init() before dispatching");
}
return NS_OK;
}
bool IsOK() const { return !!mScaleRequest; }
private:
nsAutoPtr<ScaleRequest> mScaleRequest;
WeakPtr<RasterImage> mImage;
RawAccessFrameRef mSrcRef;
RawAccessFrameRef mDstRef;
const nsIntSize mDstSize;
uint32_t mImageFlags;
ScaleState mState;
};
/* static */ StaticRefPtr<RasterImage::DecodePool> RasterImage::DecodePool::sSingleton;
@ -403,8 +367,7 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
mFinishing(false),
mInUpdateImageContainer(false),
mWantFullDecode(false),
mPendingError(false),
mScaleRequest(nullptr)
mPendingError(false)
{
mStatusTrackerInit = new imgStatusTrackerInit(this, aStatusTracker);
@ -452,6 +415,9 @@ RasterImage::~RasterImage()
}
}
// Release any HQ scaled frames from the surface cache.
SurfaceCache::Discard(this);
mAnim = nullptr;
// Total statistics
@ -1068,13 +1034,7 @@ size_t
RasterImage::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
MallocSizeOf aMallocSizeOf) const
{
size_t n = mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
if (mScaleResult.status == SCALE_DONE) {
n += mScaleResult.frame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
}
return n;
return mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
}
size_t
@ -1945,10 +1905,6 @@ RasterImage::Discard(bool force)
// Delete all the decoded frames
mFrameBlender.Discard();
// Clear our downscaled frame.
mScaleResult.status = SCALE_INVALID;
mScaleResult.frame = nullptr;
// Clear the last decoded multipart frame.
mMultipartDecodedFrame = nullptr;
@ -2521,105 +2477,85 @@ RasterImage::SyncDecode()
}
bool
RasterImage::CanQualityScale(const gfx::Size& scale)
RasterImage::CanScale(GraphicsFilter aFilter,
const nsIntSize& aSize,
uint32_t aFlags)
{
// If target size is 1:1 with original, don't scale.
if (scale.width == 1.0 && scale.height == 1.0)
#ifndef MOZ_ENABLE_SKIA
// The high-quality scaler requires Skia.
return false;
#else
// Check basic requirements: HQ downscaling is enabled, we're decoded, the
// flags allow us to do it, and a 'good' filter is being used. The flags may
// ask us not to scale because the caller isn't drawing to the window. If
// we're drawing to something else (e.g. a canvas) we usually have no way of
// updating what we've drawn, so HQ scaling is useless.
if (!gHQDownscaling || !mDecoded ||
!(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) ||
aFilter != GraphicsFilter::FILTER_GOOD) {
return false;
// To save memory don't quality upscale images bigger than the limit.
if (scale.width > 1.0 || scale.height > 1.0) {
uint32_t scaled_size = static_cast<uint32_t>(mSize.width * mSize.height * scale.width * scale.height);
if (scaled_size > gHQUpscalingMaxSize)
return false;
}
return true;
}
bool
RasterImage::CanScale(GraphicsFilter aFilter, gfx::Size aScale, uint32_t aFlags)
{
// The high-quality scaler requires Skia.
#ifdef MOZ_ENABLE_SKIA
// We don't use the scaler for animated or multipart images to avoid doing a
// bunch of work on an image that just gets thrown away.
// We only use the scaler when drawing to the window because, if we're not
// drawing to a window (eg a canvas), updates to that image will be ignored.
if (gHQDownscaling && aFilter == GraphicsFilter::FILTER_GOOD &&
!mAnim && mDecoded && !mMultipart && CanQualityScale(aScale) &&
(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING)) {
gfxFloat factor = gHQDownscalingMinFactor / 1000.0;
return (aScale.width < factor || aScale.height < factor);
if (mAnim || mMultipart) {
return false;
}
#endif
return false;
}
// If target size is 1:1 with original, don't scale.
if (aSize == mSize) {
return false;
}
void
RasterImage::ScalingStart(ScaleRequest* request)
{
MOZ_ASSERT(request);
mScaleResult.scaledSize = request->dstSize;
mScaleResult.status = SCALE_PENDING;
mScaleRequest = request;
}
void
RasterImage::ScalingDone(ScaleRequest* request, ScaleStatus status)
{
MOZ_ASSERT(status == SCALE_DONE || status == SCALE_INVALID);
MOZ_ASSERT(request);
if (status == SCALE_DONE) {
MOZ_ASSERT(request->done);
mScaleResult.status = SCALE_DONE;
mScaleResult.frame = request->dstFrame.forget();
mScaleResult.scaledSize = request->dstSize;
mScaleResult.frame->ImageUpdated(mScaleResult.frame->GetRect());
if (mStatusTracker) {
mStatusTracker->FrameChanged(&request->srcRect);
// To save memory, don't quality upscale images bigger than the limit.
if (aSize.width > mSize.width || aSize.height > mSize.height) {
uint32_t scaledSize = static_cast<uint32_t>(aSize.width * aSize.height);
if (scaledSize > gHQUpscalingMaxSize) {
return false;
}
} else {
mScaleResult.status = SCALE_INVALID;
mScaleResult.frame = nullptr;
}
// If we were waiting for this scale to come through, forget the scale
// request. Otherwise, we still have a scale outstanding that it's possible
// for us to (want to) stop.
if (mScaleRequest == request) {
mScaleRequest = nullptr;
// There's no point in scaling if we can't store the result.
if (!SurfaceCache::CanHold(aSize.ToIntSize())) {
return false;
}
// XXX(seth): It's not clear what this check buys us over gHQUpscalingMaxSize.
// The default value of this pref is 1000, which means that we never upscale.
// If that's all it's getting us, I'd rather we just forbid that explicitly.
gfx::Size scale(double(aSize.width) / mSize.width,
double(aSize.height) / mSize.height);
gfxFloat minFactor = gHQDownscalingMinFactor / 1000.0;
return (scale.width < minFactor || scale.height < minFactor);
#endif
}
void
RasterImage::NotifyNewScaledFrame()
{
if (mStatusTracker) {
// Send an invalidation so observers will repaint and can take advantage of
// the new scaled frame if possible.
// XXX(seth): Why does FrameChanged take a pointer and not a reference?
nsIntRect invalidationRect(0, 0, mSize.width, mSize.height);
mStatusTracker->FrameChanged(&invalidationRect);
}
}
void
RasterImage::RequestScale(imgFrame* aFrame, nsIntSize aSize)
RasterImage::RequestScale(imgFrame* aFrame,
uint32_t aFlags,
const nsIntSize& aSize)
{
// We can't store more than one scaled version of an image right now, so if
// there's more than one instance of this image, bail.
if (mLockCount != 1) {
return;
}
// We also can't scale if we can't lock the image data for this frame.
// We can't scale if we can't lock the image data for this frame.
RawAccessFrameRef frameRef = aFrame->RawAccessRef();
if (!frameRef) {
return;
}
// If we have an outstanding request, signal it to stop (if it can).
if (mScaleRequest) {
mScaleRequest->stopped = true;
}
nsRefPtr<ScaleRunner> runner = new ScaleRunner(this, aSize, Move(frameRef));
if (runner->IsOK()) {
nsRefPtr<ScaleRunner> runner =
new ScaleRunner(this, DecodeFlags(aFlags), aSize, Move(frameRef));
if (runner->Init()) {
if (!sScaleWorkerThread) {
NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
ClearOnShutdown(&sScaleWorkerThread);
@ -2638,24 +2574,20 @@ RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
uint32_t aFlags)
{
DrawableFrameRef frameRef;
gfx::Size scale(double(aSize.width) / mSize.width,
double(aSize.height) / mSize.height);
if (CanScale(aFilter, scale, aFlags) && !aFrameRef->IsSinglePixel()) {
// FIXME: Current implementation doesn't support pre-downscale
// mechanism for multiple sizes from same src, since we cache
// pre-downscaled frame only for the latest requested scale.
// The solution is to cache more than one scaled image frame
// for each RasterImage.
if (mScaleResult.status == SCALE_DONE && mScaleResult.scaledSize == aSize) {
frameRef = mScaleResult.frame->DrawableRef();
if (CanScale(aFilter, aSize, aFlags) && !aFrameRef->IsSinglePixel()) {
frameRef =
SurfaceCache::Lookup(ImageKey(this),
RasterSurfaceKey(aSize.ToIntSize(),
DecodeFlags(aFlags)));
if (!frameRef) {
// We either didn't have a matching scaled frame or the OS threw it away.
// Request a new one so we'll be ready next time. For now, we'll fall back
// to aFrameRef below.
RequestScale(aFrameRef.get(), aFlags, aSize);
}
if (!frameRef &&
(mScaleResult.status != SCALE_PENDING || mScaleResult.scaledSize != aSize)) {
// We either didn't have a complete scaled frame, it didn't match, or the
// OS threw it away. Fall back to aFrame, and request a new scaled frame
// if we're not already working on the one we need.
RequestScale(aFrameRef.get(), aSize);
if (frameRef && !frameRef->ImageComplete()) {
frameRef.reset(); // We're still scaling, so we can't use this yet.
}
}
@ -2669,6 +2601,8 @@ RasterImage::DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
// adjust the drawing parameters accordingly.
nsIntRect finalFrameRect = frameRef->GetRect();
if (finalFrameRect.Size() != aSize) {
gfx::Size scale(double(aSize.width) / mSize.width,
double(aSize.height) / mSize.height);
aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
region.Scale(1.0 / scale.width, 1.0 / scale.height);
}
@ -3669,22 +3603,22 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame,
}
nsIntSize destSize(ceil(aDest.width), ceil(aDest.height));
gfx::Size scale(double(destSize.width) / mSize.width,
double(destSize.height) / mSize.height);
if (CanScale(aFilter, scale, aFlags)) {
if (mScaleResult.scaledSize == destSize) {
if (mScaleResult.status == SCALE_DONE) {
if (CanScale(aFilter, destSize, aFlags)) {
DrawableFrameRef frameRef =
SurfaceCache::Lookup(ImageKey(this),
RasterSurfaceKey(destSize.ToIntSize(),
DecodeFlags(aFlags)));
if (frameRef && frameRef->ImageComplete()) {
return destSize; // We have an existing HQ scale for this size.
} else if (mScaleResult.status == SCALE_PENDING) {
return mSize; // We're waiting for exactly this result.
}
}
// If there's only one instance of this image on this page, ask for a scale.
DrawableFrameRef frameRef = GetFrame(GetRequestedFrameIndex(aWhichFrame));
if (frameRef) {
RequestScale(frameRef.get(), destSize);
if (!frameRef) {
// We could HQ scale to this size, but we haven't. Request a scale now.
frameRef = GetFrame(GetRequestedFrameIndex(aWhichFrame));
if (frameRef) {
RequestScale(frameRef.get(), aFlags, destSize);
}
}
}

Просмотреть файл

@ -132,9 +132,9 @@ class Image;
namespace image {
class ScaleRequest;
class Decoder;
class FrameAnimator;
class ScaleRunner;
class RasterImage MOZ_FINAL : public ImageResource
, public nsIProperties
@ -298,23 +298,6 @@ public:
// Called from module startup. Sets up RasterImage to be used.
static void Initialize();
enum ScaleStatus
{
SCALE_INVALID,
SCALE_PENDING,
SCALE_DONE
};
// Call this with a new ScaleRequest to mark this RasterImage's scale result
// as waiting for the results of this request. You call to ScalingDone before
// request is destroyed!
void ScalingStart(ScaleRequest* request);
// Call this with a finished ScaleRequest to set this RasterImage's scale
// result. Give it a ScaleStatus of SCALE_DONE if everything succeeded, and
// SCALE_INVALID otherwise.
void ScalingDone(ScaleRequest* request, ScaleStatus status);
// Decoder shutdown
enum eShutdownIntent {
eShutdownIntent_Done = 0,
@ -326,9 +309,6 @@ public:
// Decode strategy
private:
// Initiates an HQ scale for the given frame, if possible.
void RequestScale(imgFrame* aFrame, nsIntSize aScale);
already_AddRefed<imgStatusTracker> CurrentStatusTracker()
{
mDecodingMonitor.AssertCurrentThreadIn();
@ -724,32 +704,28 @@ private: // data
bool IsDecodeFinished();
TimeStamp mDrawStartTime;
inline bool CanQualityScale(const gfx::Size& scale);
inline bool CanScale(GraphicsFilter aFilter, gfx::Size aScale, uint32_t aFlags);
struct ScaleResult
{
ScaleResult()
: status(SCALE_INVALID)
{}
nsIntSize scaledSize;
nsRefPtr<imgFrame> frame;
ScaleStatus status;
};
ScaleResult mScaleResult;
// We hold on to a bare pointer to a ScaleRequest while it's outstanding so
// we can mark it as stopped if necessary. The ScaleWorker/DrawWorker duo
// will inform us when to let go of this pointer.
ScaleRequest* mScaleRequest;
// Initializes imgStatusTracker and resets it on RasterImage destruction.
nsAutoPtr<imgStatusTrackerInit> mStatusTrackerInit;
nsresult ShutdownDecoder(eShutdownIntent aIntent);
//////////////////////////////////////////////////////////////////////////////
// Scaling.
//////////////////////////////////////////////////////////////////////////////
// Initiates an HQ scale for the given frame, if possible.
void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize);
// Determines whether we can perform an HQ scale with the given parameters.
bool CanScale(GraphicsFilter aFilter, const nsIntSize& aSize, uint32_t aFlags);
// Called by the HQ scaler when a new scaled frame is ready.
void NotifyNewScaledFrame();
friend class ScaleRunner;
// Error handling.
void DoError();