mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1116733 (Part 1) - Allocate frames off-main-thread. r=tn
This commit is contained in:
parent
5d761e1f76
commit
a704fc2527
@ -199,12 +199,6 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
|
||||
NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
|
||||
mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
|
||||
format);
|
||||
} else {
|
||||
// Our preallocated frame matches up, with the possible exception
|
||||
// of alpha.
|
||||
if (format == gfx::SurfaceFormat::B8G8R8X8) {
|
||||
currentFrame->SetHasNoAlpha();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +227,12 @@ nsGIFDecoder2::EndImageFrame()
|
||||
mGIFStruct.screen_height - realFrameHeight);
|
||||
PostInvalidation(r);
|
||||
}
|
||||
// This transparency check is only valid for first frame
|
||||
if (mGIFStruct.is_transparent && !mSawTransparency) {
|
||||
|
||||
// The first frame was preallocated with alpha; if it wasn't transparent, we
|
||||
// should fix that. We can also mark it opaque unconditionally if we didn't
|
||||
// actually see any transparent pixels - this test is only valid for the
|
||||
// first frame.
|
||||
if (!mGIFStruct.is_transparent || !mSawTransparency) {
|
||||
opacity = Opacity::OPAQUE;
|
||||
}
|
||||
}
|
||||
|
@ -165,11 +165,6 @@ void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
|
||||
NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
|
||||
} else if (mNumFrames != 0) {
|
||||
NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
|
||||
} else {
|
||||
// Our preallocated frame matches up, with the possible exception of alpha.
|
||||
if (format == gfx::SurfaceFormat::B8G8R8X8) {
|
||||
currentFrame->SetHasNoAlpha();
|
||||
}
|
||||
}
|
||||
|
||||
mFrameRect = neededRect;
|
||||
@ -204,11 +199,9 @@ nsPNGDecoder::EndImageFrame()
|
||||
|
||||
mNumFrames++;
|
||||
|
||||
Opacity opacity;
|
||||
if (mFrameHasNoAlpha) {
|
||||
Opacity opacity = Opacity::SOME_TRANSPARENCY;
|
||||
if (format == gfx::SurfaceFormat::B8G8R8X8 || mFrameHasNoAlpha) {
|
||||
opacity = Opacity::OPAQUE;
|
||||
} else {
|
||||
opacity = Opacity::SOME_TRANSPARENCY;
|
||||
}
|
||||
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
|
@ -134,10 +134,10 @@ Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
|
||||
// Pass the data along to the implementation
|
||||
WriteInternal(aBuffer, aCount, aStrategy);
|
||||
|
||||
// If we're a synchronous decoder and we need a new frame to proceed, let's
|
||||
// create one and call it again.
|
||||
if (aStrategy == DecodeStrategy::SYNC) {
|
||||
// If we need a new frame to proceed, let's create one and call it again.
|
||||
while (NeedsNewFrame() && !HasDataError()) {
|
||||
MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode");
|
||||
|
||||
nsresult rv = AllocateFrame();
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
@ -147,7 +147,6 @@ Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
|
||||
|
||||
mNeedsToFlushData = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish telemetry.
|
||||
mDecodeTime += (TimeStamp::Now() - start);
|
||||
@ -237,7 +236,6 @@ nsresult
|
||||
Decoder::AllocateFrame()
|
||||
{
|
||||
MOZ_ASSERT(mNeedsNewFrame);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum,
|
||||
mNewFrameData.mFrameRect,
|
||||
@ -279,8 +277,6 @@ Decoder::EnsureFrame(uint32_t aFrameNum,
|
||||
uint8_t aPaletteDepth,
|
||||
imgFrame* aPreviousFrame)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mDataError || NS_FAILED(mFailCode)) {
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
@ -352,13 +348,13 @@ Decoder::InternalAddFrame(uint32_t aFrameNum,
|
||||
}
|
||||
|
||||
nsRefPtr<imgFrame> frame = new imgFrame();
|
||||
bool nonPremult =
|
||||
aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
|
||||
if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
|
||||
aPaletteDepth))) {
|
||||
aPaletteDepth, nonPremult))) {
|
||||
NS_WARNING("imgFrame::Init should succeed");
|
||||
return RawAccessFrameRef();
|
||||
}
|
||||
frame->SetAsNonPremult(aDecodeFlags &
|
||||
imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
|
||||
|
||||
RawAccessFrameRef ref = frame->RawAccessRef();
|
||||
if (!ref) {
|
||||
@ -384,11 +380,11 @@ Decoder::InternalAddFrame(uint32_t aFrameNum,
|
||||
// If we dispose of the first frame by clearing it, then the first frame's
|
||||
// refresh area is all of itself.
|
||||
// RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
|
||||
DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
|
||||
if (disposalMethod == DisposalMethod::CLEAR ||
|
||||
disposalMethod == DisposalMethod::CLEAR_ALL ||
|
||||
disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
|
||||
refreshArea = aPreviousFrame->GetRect();
|
||||
AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
|
||||
if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
|
||||
previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
|
||||
previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
|
||||
refreshArea = previousFrameData.mRect;
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,14 +477,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
|
||||
// Update our state
|
||||
mInFrame = false;
|
||||
|
||||
if (aFrameOpacity == Opacity::OPAQUE) {
|
||||
mCurrentFrame->SetHasNoAlpha();
|
||||
}
|
||||
|
||||
mCurrentFrame->SetDisposalMethod(aDisposalMethod);
|
||||
mCurrentFrame->SetRawTimeout(aTimeout);
|
||||
mCurrentFrame->SetBlendMethod(aBlendMethod);
|
||||
mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
|
||||
mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
|
||||
|
||||
mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ int32_t
|
||||
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
|
||||
{
|
||||
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
|
||||
const int32_t timeout = frame->GetRawTimeout();
|
||||
AnimationData data = frame->GetAnimationData();
|
||||
|
||||
// Ensure a minimal time between updates so we don't throttle the UI thread.
|
||||
// consider 0 == unspecified and make it fast but not too fast. Unless we
|
||||
@ -304,11 +304,11 @@ FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
|
||||
// It seems that there are broken tools out there that set a 0ms or 10ms
|
||||
// timeout when they really want a "default" one. So munge values in that
|
||||
// range.
|
||||
if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
|
||||
if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return timeout;
|
||||
return data.mRawTimeout;
|
||||
}
|
||||
|
||||
size_t
|
||||
@ -352,34 +352,34 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
|
||||
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
|
||||
|
||||
DisposalMethod prevFrameDisposalMethod = prevFrame->GetDisposalMethod();
|
||||
if (prevFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
|
||||
AnimationData prevFrameData = prevFrame->GetAnimationData();
|
||||
if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
|
||||
!mCompositingPrevFrame) {
|
||||
prevFrameDisposalMethod = DisposalMethod::CLEAR;
|
||||
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
|
||||
}
|
||||
|
||||
nsIntRect prevFrameRect = prevFrame->GetRect();
|
||||
bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
|
||||
prevFrameRect.width == mSize.width &&
|
||||
prevFrameRect.height == mSize.height);
|
||||
bool isFullPrevFrame = prevFrameData.mRect.x == 0 &&
|
||||
prevFrameData.mRect.y == 0 &&
|
||||
prevFrameData.mRect.width == mSize.width &&
|
||||
prevFrameData.mRect.height == mSize.height;
|
||||
|
||||
// Optimization: DisposeClearAll if the previous frame is the same size as
|
||||
// container and it's clearing itself
|
||||
if (isFullPrevFrame &&
|
||||
(prevFrameDisposalMethod == DisposalMethod::CLEAR)) {
|
||||
prevFrameDisposalMethod = DisposalMethod::CLEAR_ALL;
|
||||
(prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
|
||||
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
|
||||
}
|
||||
|
||||
DisposalMethod nextFrameDisposalMethod = nextFrame->GetDisposalMethod();
|
||||
nsIntRect nextFrameRect = nextFrame->GetRect();
|
||||
bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
|
||||
nextFrameRect.width == mSize.width &&
|
||||
nextFrameRect.height == mSize.height);
|
||||
AnimationData nextFrameData = nextFrame->GetAnimationData();
|
||||
bool isFullNextFrame = nextFrameData.mRect.x == 0 &&
|
||||
nextFrameData.mRect.y == 0 &&
|
||||
nextFrameData.mRect.width == mSize.width &&
|
||||
nextFrameData.mRect.height == mSize.height;
|
||||
|
||||
if (!nextFrame->GetIsPaletted()) {
|
||||
// Optimization: Skip compositing if the previous frame wants to clear the
|
||||
// whole image
|
||||
if (prevFrameDisposalMethod == DisposalMethod::CLEAR_ALL) {
|
||||
if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
return true;
|
||||
}
|
||||
@ -387,19 +387,19 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
// Optimization: Skip compositing if this frame is the same size as the
|
||||
// container and it's fully drawing over prev frame (no alpha)
|
||||
if (isFullNextFrame &&
|
||||
(nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
!nextFrame->GetHasAlpha()) {
|
||||
(nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
!nextFrameData.mHasAlpha) {
|
||||
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate area that needs updating
|
||||
switch (prevFrameDisposalMethod) {
|
||||
switch (prevFrameData.mDisposalMethod) {
|
||||
default:
|
||||
case DisposalMethod::NOT_SPECIFIED:
|
||||
case DisposalMethod::KEEP:
|
||||
*aDirtyRect = nextFrameRect;
|
||||
*aDirtyRect = nextFrameData.mRect;
|
||||
break;
|
||||
|
||||
case DisposalMethod::CLEAR_ALL:
|
||||
@ -415,7 +415,7 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
// way at the bottom, and both frames being small, we'd be
|
||||
// telling framechanged to refresh the whole image when only two
|
||||
// small areas are needed.
|
||||
aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
|
||||
aDirtyRect->UnionRect(nextFrameData.mRect, prevFrameData.mRect);
|
||||
break;
|
||||
|
||||
case DisposalMethod::RESTORE_PREVIOUS:
|
||||
@ -453,6 +453,8 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
needToBlankComposite = true;
|
||||
}
|
||||
|
||||
AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
|
||||
|
||||
// More optimizations possible when next frame is not transparent
|
||||
// But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
|
||||
// this "no disposal" optimization is not possible,
|
||||
@ -460,8 +462,8 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
// needs to be stored in compositingFrame, so it can be
|
||||
// copied into compositingPrevFrame later.
|
||||
bool doDisposal = true;
|
||||
if (!nextFrame->GetHasAlpha() &&
|
||||
nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
|
||||
if (!nextFrameData.mHasAlpha &&
|
||||
nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
|
||||
if (isFullNextFrame) {
|
||||
// Optimization: No need to dispose prev.frame when
|
||||
// next frame is full frame and not transparent.
|
||||
@ -469,12 +471,12 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
// No need to blank the composite frame
|
||||
needToBlankComposite = false;
|
||||
} else {
|
||||
if ((prevFrameRect.x >= nextFrameRect.x) &&
|
||||
(prevFrameRect.y >= nextFrameRect.y) &&
|
||||
(prevFrameRect.x + prevFrameRect.width <=
|
||||
nextFrameRect.x + nextFrameRect.width) &&
|
||||
(prevFrameRect.y + prevFrameRect.height <=
|
||||
nextFrameRect.y + nextFrameRect.height)) {
|
||||
if ((prevFrameData.mRect.x >= nextFrameData.mRect.x) &&
|
||||
(prevFrameData.mRect.y >= nextFrameData.mRect.y) &&
|
||||
(prevFrameData.mRect.x + prevFrameData.mRect.width <=
|
||||
nextFrameData.mRect.x + nextFrameData.mRect.width) &&
|
||||
(prevFrameData.mRect.y + prevFrameData.mRect.height <=
|
||||
nextFrameData.mRect.y + nextFrameData.mRect.height)) {
|
||||
// Optimization: No need to dispose prev.frame when
|
||||
// next frame fully overlaps previous frame.
|
||||
doDisposal = false;
|
||||
@ -484,43 +486,46 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
|
||||
if (doDisposal) {
|
||||
// Dispose of previous: clear, restore, or keep (copy)
|
||||
switch (prevFrameDisposalMethod) {
|
||||
switch (prevFrameData.mDisposalMethod) {
|
||||
case DisposalMethod::CLEAR:
|
||||
if (needToBlankComposite) {
|
||||
// If we just created the composite, it could have anything in its
|
||||
// buffer. Clear whole frame
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
} else {
|
||||
// Only blank out previous frame area (both color & Mask/Alpha)
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect(),
|
||||
prevFrameRect);
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
prevFrameData.mRect);
|
||||
}
|
||||
break;
|
||||
|
||||
case DisposalMethod::CLEAR_ALL:
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
break;
|
||||
|
||||
case DisposalMethod::RESTORE_PREVIOUS:
|
||||
// It would be better to copy only the area changed back to
|
||||
// compositingFrame.
|
||||
if (mCompositingPrevFrame) {
|
||||
CopyFrameImage(mCompositingPrevFrame->GetRawData(),
|
||||
mCompositingPrevFrame->GetRect(),
|
||||
mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
AnimationData compositingPrevFrameData =
|
||||
mCompositingPrevFrame->GetAnimationData();
|
||||
|
||||
CopyFrameImage(compositingPrevFrameData.mRawData,
|
||||
compositingPrevFrameData.mRect,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
|
||||
// destroy only if we don't need it for this frame's disposal
|
||||
if (nextFrameDisposalMethod !=
|
||||
if (nextFrameData.mDisposalMethod !=
|
||||
DisposalMethod::RESTORE_PREVIOUS) {
|
||||
mCompositingPrevFrame.reset();
|
||||
}
|
||||
} else {
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -535,39 +540,39 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
|
||||
if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
|
||||
// Just copy the bits
|
||||
CopyFrameImage(prevFrame->GetRawData(),
|
||||
prevFrame->GetRect(),
|
||||
mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
CopyFrameImage(prevFrameData.mRawData,
|
||||
prevFrameData.mRect,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
} else {
|
||||
if (needToBlankComposite) {
|
||||
// Only blank composite when prev is transparent or not full.
|
||||
if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
}
|
||||
DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
|
||||
prevFrame->PaletteDataLength(),
|
||||
prevFrame->GetHasAlpha(),
|
||||
mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect(),
|
||||
prevFrame->GetBlendMethod());
|
||||
DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
|
||||
prevFrameData.mPaletteDataLength,
|
||||
prevFrameData.mHasAlpha,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
prevFrameData.mBlendMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (needToBlankComposite) {
|
||||
// If we just created the composite, it could have anything in its
|
||||
// buffers. Clear them
|
||||
ClearFrame(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect());
|
||||
ClearFrame(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect);
|
||||
}
|
||||
|
||||
// Check if the frame we are composing wants the previous image restored after
|
||||
// it is done. Don't store it (again) if last frame wanted its image restored
|
||||
// too
|
||||
if ((nextFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
(prevFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
|
||||
if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
|
||||
(prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
|
||||
// We are storing the whole image.
|
||||
// It would be better if we just stored the area that nextFrame is going to
|
||||
// overwrite.
|
||||
@ -583,28 +588,26 @@ FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
|
||||
mCompositingPrevFrame = newFrame->RawAccessRef();
|
||||
}
|
||||
|
||||
CopyFrameImage(mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect(),
|
||||
mCompositingPrevFrame->GetRawData(),
|
||||
mCompositingPrevFrame->GetRect());
|
||||
AnimationData compositingPrevFrameData =
|
||||
mCompositingPrevFrame->GetAnimationData();
|
||||
|
||||
CopyFrameImage(compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
compositingPrevFrameData.mRawData,
|
||||
compositingPrevFrameData.mRect);
|
||||
}
|
||||
|
||||
// blit next frame into it's correct spot
|
||||
DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
|
||||
nextFrame->PaletteDataLength(),
|
||||
nextFrame->GetHasAlpha(),
|
||||
mCompositingFrame->GetRawData(),
|
||||
mCompositingFrame->GetRect(),
|
||||
nextFrame->GetBlendMethod());
|
||||
|
||||
// Set timeout of CompositeFrame to timeout of frame we just composed
|
||||
// Bug 177948
|
||||
int32_t timeout = nextFrame->GetRawTimeout();
|
||||
mCompositingFrame->SetRawTimeout(timeout);
|
||||
DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
|
||||
nextFrameData.mPaletteDataLength,
|
||||
nextFrameData.mHasAlpha,
|
||||
compositingFrameData.mRawData,
|
||||
compositingFrameData.mRect,
|
||||
nextFrameData.mBlendMethod);
|
||||
|
||||
// Tell the image that it is fully 'downloaded'.
|
||||
nsresult rv =
|
||||
mCompositingFrame->ImageUpdated(mCompositingFrame->GetRect());
|
||||
mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -199,18 +199,14 @@ public:
|
||||
{
|
||||
if (mState == eReady) {
|
||||
// Collect information from the frames that we need to scale.
|
||||
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();
|
||||
ScalingData srcData = mSrcRef->GetScalingData();
|
||||
ScalingData dstData = mDstRef->GetScalingData();
|
||||
|
||||
// Actually do the scaling.
|
||||
bool succeeded =
|
||||
gfx::Scale(srcData, srcSize.width, srcSize.height, srcStride,
|
||||
dstData, mDstSize.width, mDstSize.height, dstStride,
|
||||
srcFormat);
|
||||
gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
|
||||
srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
|
||||
mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
|
||||
|
||||
if (succeeded) {
|
||||
// Mark the frame as complete and discardable.
|
||||
@ -657,6 +653,13 @@ RasterImage::IsOpaque()
|
||||
void
|
||||
RasterImage::OnSurfaceDiscarded()
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &RasterImage::OnSurfaceDiscarded);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mProgressTracker) {
|
||||
mProgressTracker->OnDiscard();
|
||||
}
|
||||
@ -930,11 +933,42 @@ RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation,
|
||||
return n;
|
||||
}
|
||||
|
||||
class OnAddedFrameRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
OnAddedFrameRunnable(RasterImage* aImage,
|
||||
uint32_t aNewFrameCount,
|
||||
const nsIntRect& aNewRefreshArea)
|
||||
: mImage(aImage)
|
||||
, mNewFrameCount(aNewFrameCount)
|
||||
, mNewRefreshArea(aNewRefreshArea)
|
||||
{
|
||||
MOZ_ASSERT(aImage);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
mImage->OnAddedFrame(mNewFrameCount, mNewRefreshArea);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<RasterImage> mImage;
|
||||
uint32_t mNewFrameCount;
|
||||
nsIntRect mNewRefreshArea;
|
||||
};
|
||||
|
||||
void
|
||||
RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
|
||||
const nsIntRect& aNewRefreshArea)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
new OnAddedFrameRunnable(this, aNewFrameCount, aNewRefreshArea);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
|
||||
mFrameCount < aNewFrameCount,
|
||||
"Frame count running backwards");
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
@ -119,7 +120,8 @@ class CachedSurface
|
||||
{
|
||||
~CachedSurface() {}
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(CachedSurface)
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
|
||||
|
||||
CachedSurface(imgFrame* aSurface,
|
||||
const Cost aCost,
|
||||
@ -215,7 +217,8 @@ class ImageSurfaceCache
|
||||
public:
|
||||
ImageSurfaceCache() : mLocked(false) { }
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
|
||||
|
||||
typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
|
||||
|
||||
@ -277,6 +280,7 @@ public:
|
||||
: mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
|
||||
aSurfaceCacheExpirationTimeMS)
|
||||
, mMemoryPressureObserver(new MemoryPressureObserver)
|
||||
, mMutex("SurfaceCache")
|
||||
, mDiscardFactor(aSurfaceCacheDiscardFactor)
|
||||
, mMaxCost(aSurfaceCacheSize)
|
||||
, mAvailableCost(aSurfaceCacheSize)
|
||||
@ -298,9 +302,9 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
void InitMemoryReporter() {
|
||||
RegisterWeakMemoryReporter(this);
|
||||
}
|
||||
void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
|
||||
|
||||
Mutex& GetMutex() { return mMutex; }
|
||||
|
||||
bool Insert(imgFrame* aSurface,
|
||||
const Cost aCost,
|
||||
@ -705,6 +709,7 @@ private:
|
||||
nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
|
||||
SurfaceTracker mExpirationTracker;
|
||||
nsRefPtr<MemoryPressureObserver> mMemoryPressureObserver;
|
||||
Mutex mMutex;
|
||||
const uint32_t mDiscardFactor;
|
||||
const Cost mMaxCost;
|
||||
Cost mAvailableCost;
|
||||
@ -722,6 +727,7 @@ NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
|
||||
SurfaceCache::Initialize()
|
||||
{
|
||||
// Initialize preferences.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
|
||||
|
||||
// See gfxPrefs for the default values of these preferences.
|
||||
@ -775,6 +781,7 @@ SurfaceCache::Initialize()
|
||||
/* static */ void
|
||||
SurfaceCache::Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
|
||||
sInstance = nullptr;
|
||||
}
|
||||
@ -783,11 +790,11 @@ SurfaceCache::Shutdown()
|
||||
SurfaceCache::Lookup(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInstance) {
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
return sInstance->Lookup(aImageKey, aSurfaceKey);
|
||||
}
|
||||
|
||||
@ -797,11 +804,11 @@ SurfaceCache::Insert(imgFrame* aSurface,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
Lifetime aLifetime)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
Cost cost = ComputeCost(aSurfaceKey.Size());
|
||||
return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
|
||||
}
|
||||
@ -809,7 +816,6 @@ SurfaceCache::Insert(imgFrame* aSurface,
|
||||
/* static */ bool
|
||||
SurfaceCache::CanHold(const IntSize& aSize)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInstance) {
|
||||
return false;
|
||||
}
|
||||
@ -821,8 +827,8 @@ SurfaceCache::CanHold(const IntSize& aSize)
|
||||
/* static */ void
|
||||
SurfaceCache::LockImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
return sInstance->LockImage(aImageKey);
|
||||
}
|
||||
}
|
||||
@ -830,8 +836,8 @@ SurfaceCache::LockImage(Image* aImageKey)
|
||||
/* static */ void
|
||||
SurfaceCache::UnlockImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
return sInstance->UnlockImage(aImageKey);
|
||||
}
|
||||
}
|
||||
@ -840,8 +846,8 @@ SurfaceCache::UnlockImage(Image* aImageKey)
|
||||
SurfaceCache::RemoveSurface(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
sInstance->RemoveSurface(aImageKey, aSurfaceKey);
|
||||
}
|
||||
}
|
||||
@ -849,8 +855,8 @@ SurfaceCache::RemoveSurface(const ImageKey aImageKey,
|
||||
/* static */ void
|
||||
SurfaceCache::RemoveImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
sInstance->RemoveImage(aImageKey);
|
||||
}
|
||||
}
|
||||
@ -858,8 +864,8 @@ SurfaceCache::RemoveImage(Image* aImageKey)
|
||||
/* static */ void
|
||||
SurfaceCache::DiscardAll()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
sInstance->DiscardAll();
|
||||
}
|
||||
}
|
||||
@ -869,11 +875,11 @@ SurfaceCache::SizeOfSurfaces(const ImageKey aImageKey,
|
||||
gfxMemoryLocation aLocation,
|
||||
MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInstance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(sInstance->GetMutex());
|
||||
return sInstance->SizeOfSurfaces(aImageKey, aLocation, aMallocSizeOf);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ static bool gDisableOptimize = false;
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "nsMargin.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/gfx/Tools.h"
|
||||
|
||||
@ -128,19 +129,20 @@ static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize,
|
||||
}
|
||||
|
||||
|
||||
imgFrame::imgFrame() :
|
||||
mDecoded(0, 0, 0, 0),
|
||||
mDecodedMutex("imgFrame::mDecoded"),
|
||||
mPalettedImageData(nullptr),
|
||||
mTimeout(100),
|
||||
mLockCount(0),
|
||||
mDisposalMethod(DisposalMethod::NOT_SPECIFIED),
|
||||
mBlendMethod(BlendMethod::OVER),
|
||||
mSinglePixel(false),
|
||||
mCompositingFailed(false),
|
||||
mHasNoAlpha(false),
|
||||
mNonPremult(false),
|
||||
mOptimizable(false)
|
||||
imgFrame::imgFrame()
|
||||
: mMutex("imgFrame")
|
||||
, mDecoded(0, 0, 0, 0)
|
||||
, mLockCount(0)
|
||||
, mTimeout(100)
|
||||
, mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
|
||||
, mBlendMethod(BlendMethod::OVER)
|
||||
, mHasNoAlpha(false)
|
||||
, mPalettedImageData(nullptr)
|
||||
, mPaletteDepth(0)
|
||||
, mNonPremult(false)
|
||||
, mSinglePixel(false)
|
||||
, mCompositingFailed(false)
|
||||
, mOptimizable(false)
|
||||
{
|
||||
static bool hasCheckedOptimize = false;
|
||||
if (!hasCheckedOptimize) {
|
||||
@ -161,7 +163,8 @@ nsresult
|
||||
imgFrame::InitForDecoder(const nsIntSize& aImageSize,
|
||||
const nsIntRect& aRect,
|
||||
SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth /* = 0 */)
|
||||
uint8_t aPaletteDepth /* = 0 */,
|
||||
bool aNonPremult /* = false */)
|
||||
{
|
||||
// Assert for properties that should be verified by decoders,
|
||||
// warn for properties related to bad content.
|
||||
@ -176,6 +179,7 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
|
||||
|
||||
mFormat = aFormat;
|
||||
mPaletteDepth = aPaletteDepth;
|
||||
mNonPremult = aNonPremult;
|
||||
|
||||
if (aPaletteDepth != 0) {
|
||||
// We're creating for a paletted image.
|
||||
@ -185,8 +189,11 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Use the fallible allocator here
|
||||
mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
|
||||
// Use the fallible allocator here. Paletted images always use 1 byte per
|
||||
// pixel, so calculating the amount of memory we need is straightforward.
|
||||
mPalettedImageData =
|
||||
static_cast<uint8_t*>(moz_malloc(PaletteDataLength() +
|
||||
(mSize.width * mSize.height)));
|
||||
if (!mPalettedImageData)
|
||||
NS_WARNING("moz_malloc for paletted image data should succeed");
|
||||
NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
|
||||
@ -300,6 +307,7 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
|
||||
nsresult imgFrame::Optimize()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mLockCount == 1,
|
||||
"Should only optimize when holding the lock exclusively");
|
||||
|
||||
@ -359,7 +367,8 @@ nsresult imgFrame::Optimize()
|
||||
SurfaceFormat optFormat =
|
||||
gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR);
|
||||
|
||||
if (!GetHasAlpha() && optFormat == SurfaceFormat::R5G6B5) {
|
||||
if (mFormat != SurfaceFormat::B8G8R8A8 &&
|
||||
optFormat == SurfaceFormat::R5G6B5) {
|
||||
RefPtr<VolatileBuffer> buf =
|
||||
AllocateBufferForImage(mSize, optFormat);
|
||||
if (!buf)
|
||||
@ -428,7 +437,8 @@ imgFrame::RawAccessRef()
|
||||
void
|
||||
imgFrame::SetRawAccessOnly()
|
||||
{
|
||||
MOZ_ASSERT(mLockCount > 0, "Must hold a RawAccessFrameRef");
|
||||
AssertImageDataLocked();
|
||||
|
||||
// Lock our data and throw away the key.
|
||||
LockImageData();
|
||||
}
|
||||
@ -444,6 +454,9 @@ imgFrame::SurfaceForDrawing(bool aDoPadding,
|
||||
ImageRegion& aRegion,
|
||||
SourceSurface* aSurface)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
|
||||
if (!aDoPadding && !aDoPartialDecode) {
|
||||
NS_ASSERTION(!mSinglePixel, "This should already have been handled");
|
||||
@ -496,19 +509,22 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
|
||||
PROFILER_LABEL("imgFrame", "Draw",
|
||||
js::ProfileEntry::Category::GRAPHICS);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
|
||||
NS_ASSERTION(!aRegion.IsRestricted() ||
|
||||
!aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
|
||||
"We must be allowed to sample *some* source pixels!");
|
||||
NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsIntMargin padding(mOffset.y,
|
||||
mImageSize.width - (mOffset.x + mSize.width),
|
||||
mImageSize.height - (mOffset.y + mSize.height),
|
||||
mOffset.x);
|
||||
|
||||
bool doPadding = padding != nsIntMargin(0,0,0,0);
|
||||
bool doPartialDecode = !ImageComplete();
|
||||
bool doPartialDecode = !ImageCompleteInternal();
|
||||
|
||||
if (mSinglePixel && !doPadding && !doPartialDecode) {
|
||||
if (mSinglePixelColor.a == 0.0) {
|
||||
@ -522,7 +538,7 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
|
||||
return true;
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> surf = GetSurface();
|
||||
RefPtr<SourceSurface> surf = GetSurfaceInternal();
|
||||
if (!surf && !mSinglePixel) {
|
||||
return false;
|
||||
}
|
||||
@ -550,10 +566,17 @@ bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
|
||||
return true;
|
||||
}
|
||||
|
||||
// This can be called from any thread, but not simultaneously.
|
||||
nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
|
||||
nsresult
|
||||
imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
|
||||
{
|
||||
MutexAutoLock lock(mDecodedMutex);
|
||||
MutexAutoLock lock(mMutex);
|
||||
return ImageUpdatedInternal(aUpdateRect);
|
||||
}
|
||||
|
||||
nsresult
|
||||
imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
mDecoded.UnionRect(mDecoded, aUpdateRect);
|
||||
|
||||
@ -565,6 +588,23 @@ nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
|
||||
int32_t aRawTimeout, BlendMethod aBlendMethod)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
|
||||
|
||||
if (aFrameOpacity == Opacity::OPAQUE) {
|
||||
mHasNoAlpha = true;
|
||||
}
|
||||
|
||||
mDisposalMethod = aDisposalMethod;
|
||||
mTimeout = aRawTimeout;
|
||||
mBlendMethod = aBlendMethod;
|
||||
ImageUpdatedInternal(GetRect());
|
||||
}
|
||||
|
||||
nsIntRect imgFrame::GetRect() const
|
||||
{
|
||||
return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height));
|
||||
@ -573,6 +613,8 @@ nsIntRect imgFrame::GetRect() const
|
||||
int32_t
|
||||
imgFrame::GetStride() const
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
if (mImageSurface) {
|
||||
return mImageSurface->Stride();
|
||||
}
|
||||
@ -582,11 +624,14 @@ imgFrame::GetStride() const
|
||||
|
||||
SurfaceFormat imgFrame::GetFormat() const
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
uint32_t imgFrame::GetImageBytesPerRow() const
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
if (mVBuf)
|
||||
return mSize.width * BytesPerPixel(mFormat);
|
||||
|
||||
@ -601,18 +646,31 @@ uint32_t imgFrame::GetImageDataLength() const
|
||||
return GetImageBytesPerRow() * mSize.height;
|
||||
}
|
||||
|
||||
void imgFrame::GetImageData(uint8_t **aData, uint32_t *length) const
|
||||
void
|
||||
imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetImageData unless frame is locked");
|
||||
MutexAutoLock lock(mMutex);
|
||||
GetImageDataInternal(aData, aLength);
|
||||
}
|
||||
|
||||
if (mImageSurface)
|
||||
void
|
||||
imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
|
||||
|
||||
if (mImageSurface) {
|
||||
*aData = mVBufPtr;
|
||||
else if (mPalettedImageData)
|
||||
MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData");
|
||||
} else if (mPalettedImageData) {
|
||||
*aData = mPalettedImageData + PaletteDataLength();
|
||||
else
|
||||
MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData");
|
||||
} else {
|
||||
MOZ_ASSERT(false, "Have neither mImageSurface nor mPalettedImageData in GetImageData");
|
||||
*aData = nullptr;
|
||||
}
|
||||
|
||||
*length = GetImageDataLength();
|
||||
*aLength = GetImageDataLength();
|
||||
}
|
||||
|
||||
uint8_t* imgFrame::GetImageData() const
|
||||
@ -628,14 +686,9 @@ bool imgFrame::GetIsPaletted() const
|
||||
return mPalettedImageData != nullptr;
|
||||
}
|
||||
|
||||
bool imgFrame::GetHasAlpha() const
|
||||
{
|
||||
return mFormat == SurfaceFormat::B8G8R8A8;
|
||||
}
|
||||
|
||||
void imgFrame::GetPaletteData(uint32_t **aPalette, uint32_t *length) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mLockCount != 0, "Can't GetPaletteData unless frame is locked");
|
||||
AssertImageDataLocked();
|
||||
|
||||
if (!mPalettedImageData) {
|
||||
*aPalette = nullptr;
|
||||
@ -654,21 +707,12 @@ uint32_t* imgFrame::GetPaletteData() const
|
||||
return data;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
imgFrame::GetRawData() const
|
||||
nsresult
|
||||
imgFrame::LockImageData()
|
||||
{
|
||||
MOZ_ASSERT(mLockCount, "Should be locked to call GetRawData()");
|
||||
if (mPalettedImageData) {
|
||||
return mPalettedImageData;
|
||||
}
|
||||
return GetImageData();
|
||||
}
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsresult imgFrame::LockImageData()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
|
||||
MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
|
||||
if (mLockCount < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -680,9 +724,26 @@ nsresult imgFrame::LockImageData()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Paletted images don't have surfaces, so there's nothing to do.
|
||||
if (mPalettedImageData)
|
||||
// If we're the first lock, but have an image surface, we're OK.
|
||||
if (mImageSurface) {
|
||||
mVBufPtr = mVBuf;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Paletted images don't have surfaces, so there's nothing to do.
|
||||
if (mPalettedImageData) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return Deoptimize();
|
||||
}
|
||||
|
||||
nsresult
|
||||
imgFrame::Deoptimize()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(!mImageSurface);
|
||||
|
||||
if (!mImageSurface) {
|
||||
if (mVBuf) {
|
||||
@ -746,9 +807,34 @@ nsresult imgFrame::LockImageData()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult imgFrame::UnlockImageData()
|
||||
void
|
||||
imgFrame::AssertImageDataLocked() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
#ifdef DEBUG
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
|
||||
#endif
|
||||
}
|
||||
|
||||
class UnlockImageDataRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
UnlockImageDataRunnable(imgFrame* aTarget)
|
||||
: mTarget(aTarget)
|
||||
{
|
||||
MOZ_ASSERT(mTarget);
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() { return mTarget->UnlockImageData(); }
|
||||
|
||||
private:
|
||||
nsRefPtr<imgFrame> mTarget;
|
||||
};
|
||||
|
||||
nsresult
|
||||
imgFrame::UnlockImageData()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
|
||||
if (mLockCount <= 0) {
|
||||
@ -759,6 +845,13 @@ nsresult imgFrame::UnlockImageData()
|
||||
// surface anymore. (But we don't need to do anything for paletted images,
|
||||
// which don't have surfaces.)
|
||||
if (mLockCount == 1 && !mPalettedImageData) {
|
||||
// We can't safely optimize off-main-thread, so create a runnable to do it.
|
||||
if (!NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If we're using a surface format with alpha but the image has no alpha,
|
||||
// change the format. This doesn't change the underlying data at all, but
|
||||
// allows DrawTargets to avoid blending when drawing known opaque images.
|
||||
@ -783,13 +876,37 @@ nsresult imgFrame::UnlockImageData()
|
||||
void
|
||||
imgFrame::SetOptimizable()
|
||||
{
|
||||
MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
AssertImageDataLocked();
|
||||
mOptimizable = true;
|
||||
}
|
||||
|
||||
Color
|
||||
imgFrame::SinglePixelColor() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mSinglePixelColor;
|
||||
}
|
||||
|
||||
bool
|
||||
imgFrame::IsSinglePixel() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mSinglePixel;
|
||||
}
|
||||
|
||||
TemporaryRef<SourceSurface>
|
||||
imgFrame::GetSurface()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
return GetSurfaceInternal();
|
||||
}
|
||||
|
||||
TemporaryRef<SourceSurface>
|
||||
imgFrame::GetSurfaceInternal()
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
if (mOptSurface) {
|
||||
if (mOptSurface->IsValid())
|
||||
return mOptSurface;
|
||||
@ -813,9 +930,11 @@ imgFrame::GetSurface()
|
||||
TemporaryRef<DrawTarget>
|
||||
imgFrame::GetDrawTarget()
|
||||
{
|
||||
MOZ_ASSERT(mLockCount >= 1, "Should lock before requesting a DrawTarget");
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
uint8_t* data = GetImageData();
|
||||
uint8_t* data;
|
||||
uint32_t length;
|
||||
GetImageDataInternal(&data, &length);
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -825,45 +944,64 @@ imgFrame::GetDrawTarget()
|
||||
CreateDrawTargetForData(data, mSize, stride, mFormat);
|
||||
}
|
||||
|
||||
int32_t imgFrame::GetRawTimeout() const
|
||||
AnimationData
|
||||
imgFrame::GetAnimationData() const
|
||||
{
|
||||
return mTimeout;
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
|
||||
|
||||
uint8_t* data;
|
||||
if (mPalettedImageData) {
|
||||
data = mPalettedImageData;
|
||||
} else {
|
||||
uint32_t length;
|
||||
GetImageDataInternal(&data, &length);
|
||||
}
|
||||
|
||||
void imgFrame::SetRawTimeout(int32_t aTimeout)
|
||||
{
|
||||
mTimeout = aTimeout;
|
||||
bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8;
|
||||
|
||||
return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
|
||||
mBlendMethod, mDisposalMethod, hasAlpha);
|
||||
}
|
||||
|
||||
// This can be called from any thread.
|
||||
bool imgFrame::ImageComplete() const
|
||||
ScalingData
|
||||
imgFrame::GetScalingData() const
|
||||
{
|
||||
MutexAutoLock lock(mDecodedMutex);
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
|
||||
MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images");
|
||||
|
||||
uint8_t* data;
|
||||
uint32_t length;
|
||||
GetImageDataInternal(&data, &length);
|
||||
|
||||
return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat);
|
||||
}
|
||||
|
||||
bool
|
||||
imgFrame::ImageComplete() const
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
return ImageCompleteInternal();
|
||||
}
|
||||
|
||||
bool
|
||||
imgFrame::ImageCompleteInternal() const
|
||||
{
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
|
||||
mSize.width, mSize.height));
|
||||
}
|
||||
|
||||
// A hint from the image decoders that this image has no alpha, even
|
||||
// though we're decoding it as B8G8R8A8.
|
||||
void imgFrame::SetHasNoAlpha()
|
||||
{
|
||||
MOZ_ASSERT(mLockCount, "Expected to be locked when SetHasNoAlpha is called");
|
||||
mHasNoAlpha = true;
|
||||
}
|
||||
|
||||
void imgFrame::SetAsNonPremult(bool aIsNonPremult)
|
||||
{
|
||||
mNonPremult = aIsNonPremult;
|
||||
}
|
||||
|
||||
bool imgFrame::GetCompositingFailed() const
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mCompositingFailed;
|
||||
}
|
||||
|
||||
void imgFrame::SetCompositingFailed(bool val)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mCompositingFailed = val;
|
||||
}
|
||||
|
||||
@ -871,6 +1009,8 @@ size_t
|
||||
imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
|
||||
MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
// aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP. It
|
||||
// should be nullptr otherwise.
|
||||
NS_ABORT_IF_FALSE(
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/VolatileBuffer.h"
|
||||
#include "gfxDrawable.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "MainThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
@ -45,6 +46,65 @@ MOZ_BEGIN_ENUM_CLASS(Opacity, uint8_t)
|
||||
SOME_TRANSPARENCY
|
||||
MOZ_END_ENUM_CLASS(Opacity)
|
||||
|
||||
|
||||
/**
|
||||
* AnimationData contains all of the information necessary for using an imgFrame
|
||||
* as part of an animation.
|
||||
*
|
||||
* It includes pointers to the raw image data of the underlying imgFrame, but
|
||||
* does not own that data. A RawAccessFrameRef for the underlying imgFrame must
|
||||
* outlive the AnimationData for it to remain valid.
|
||||
*/
|
||||
struct AnimationData
|
||||
{
|
||||
AnimationData(uint8_t* aRawData, uint32_t aPaletteDataLength,
|
||||
int32_t aRawTimeout, const nsIntRect& aRect,
|
||||
BlendMethod aBlendMethod, DisposalMethod aDisposalMethod,
|
||||
bool aHasAlpha)
|
||||
: mRawData(aRawData)
|
||||
, mPaletteDataLength(aPaletteDataLength)
|
||||
, mRawTimeout(aRawTimeout)
|
||||
, mRect(aRect)
|
||||
, mBlendMethod(aBlendMethod)
|
||||
, mDisposalMethod(aDisposalMethod)
|
||||
, mHasAlpha(aHasAlpha)
|
||||
{ }
|
||||
|
||||
uint8_t* mRawData;
|
||||
uint32_t mPaletteDataLength;
|
||||
int32_t mRawTimeout;
|
||||
nsIntRect mRect;
|
||||
BlendMethod mBlendMethod;
|
||||
DisposalMethod mDisposalMethod;
|
||||
bool mHasAlpha;
|
||||
};
|
||||
|
||||
/**
|
||||
* ScalingData contains all of the information necessary for performing
|
||||
* high-quality (CPU-based) scaling an imgFrame.
|
||||
*
|
||||
* It includes pointers to the raw image data of the underlying imgFrame, but
|
||||
* does not own that data. A RawAccessFrameRef for the underlying imgFrame must
|
||||
* outlive the ScalingData for it to remain valid.
|
||||
*/
|
||||
struct ScalingData
|
||||
{
|
||||
ScalingData(uint8_t* aRawData,
|
||||
gfx::IntSize aSize,
|
||||
uint32_t aBytesPerRow,
|
||||
gfx::SurfaceFormat aFormat)
|
||||
: mRawData(aRawData)
|
||||
, mSize(aSize)
|
||||
, mBytesPerRow(aBytesPerRow)
|
||||
, mFormat(aFormat)
|
||||
{ }
|
||||
|
||||
uint8_t* mRawData;
|
||||
gfx::IntSize mSize;
|
||||
uint32_t mBytesPerRow;
|
||||
gfx::SurfaceFormat mFormat;
|
||||
};
|
||||
|
||||
class imgFrame
|
||||
{
|
||||
typedef gfx::Color Color;
|
||||
@ -71,7 +131,8 @@ public:
|
||||
nsresult InitForDecoder(const nsIntSize& aImageSize,
|
||||
const nsIntRect& aRect,
|
||||
SurfaceFormat aFormat,
|
||||
uint8_t aPaletteDepth = 0);
|
||||
uint8_t aPaletteDepth = 0,
|
||||
bool aNonPremult = false);
|
||||
|
||||
nsresult InitForDecoder(const nsIntSize& aSize,
|
||||
SurfaceFormat aFormat,
|
||||
@ -118,71 +179,61 @@ public:
|
||||
|
||||
nsresult ImageUpdated(const nsIntRect &aUpdateRect);
|
||||
|
||||
/**
|
||||
* Mark this imgFrame as completely decoded, and set final options.
|
||||
*
|
||||
* @param aFrameOpacity Whether this imgFrame is opaque.
|
||||
* @param aDisposalMethod For animation frames, how this imgFrame is cleared
|
||||
* from the compositing frame before the next frame is
|
||||
* displayed.
|
||||
* @param aRawTimeout For animation frames, the timeout in milliseconds
|
||||
* before the next frame is displayed. This timeout is
|
||||
* not necessarily the timeout that will actually be
|
||||
* used; see FrameAnimator::GetTimeoutForFrame.
|
||||
* @param aBlendMethod For animation frames, a blending method to be used
|
||||
* when compositing this frame.
|
||||
*/
|
||||
void Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
|
||||
int32_t aRawTimeout, BlendMethod aBlendMethod);
|
||||
|
||||
IntSize GetImageSize() { return mImageSize; }
|
||||
nsIntRect GetRect() const;
|
||||
IntSize GetSize() const { return mSize; }
|
||||
bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
|
||||
int32_t GetStride() const;
|
||||
SurfaceFormat GetFormat() const;
|
||||
uint32_t GetImageBytesPerRow() const;
|
||||
uint32_t GetImageDataLength() const;
|
||||
bool GetIsPaletted() const;
|
||||
bool GetHasAlpha() const;
|
||||
void GetImageData(uint8_t **aData, uint32_t *length) const;
|
||||
uint8_t* GetImageData() const;
|
||||
|
||||
bool GetIsPaletted() const;
|
||||
void GetPaletteData(uint32_t **aPalette, uint32_t *length) const;
|
||||
uint32_t* GetPaletteData() const;
|
||||
uint8_t* GetRawData() const;
|
||||
uint8_t GetPaletteDepth() const { return mPaletteDepth; }
|
||||
|
||||
int32_t GetRawTimeout() const;
|
||||
void SetRawTimeout(int32_t aTimeout);
|
||||
/**
|
||||
* Get the SurfaceFormat for this imgFrame.
|
||||
*
|
||||
* This should only be used for assertions.
|
||||
*/
|
||||
SurfaceFormat GetFormat() const;
|
||||
|
||||
DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
|
||||
void SetDisposalMethod(DisposalMethod aDisposalMethod)
|
||||
{
|
||||
mDisposalMethod = aDisposalMethod;
|
||||
}
|
||||
|
||||
BlendMethod GetBlendMethod() const { return mBlendMethod; }
|
||||
void SetBlendMethod(BlendMethod aBlendMethod) { mBlendMethod = aBlendMethod; }
|
||||
AnimationData GetAnimationData() const;
|
||||
ScalingData GetScalingData() const;
|
||||
|
||||
bool ImageComplete() const;
|
||||
|
||||
void SetHasNoAlpha();
|
||||
void SetAsNonPremult(bool aIsNonPremult);
|
||||
|
||||
bool GetCompositingFailed() const;
|
||||
void SetCompositingFailed(bool val);
|
||||
|
||||
void SetOptimizable();
|
||||
|
||||
Color SinglePixelColor() const;
|
||||
bool IsSinglePixel() const;
|
||||
|
||||
TemporaryRef<SourceSurface> GetSurface();
|
||||
TemporaryRef<DrawTarget> GetDrawTarget();
|
||||
|
||||
Color
|
||||
SinglePixelColor()
|
||||
{
|
||||
return mSinglePixelColor;
|
||||
}
|
||||
|
||||
bool IsSinglePixel()
|
||||
{
|
||||
return mSinglePixel;
|
||||
}
|
||||
|
||||
TemporaryRef<SourceSurface> CachedSurface();
|
||||
|
||||
size_t SizeOfExcludingThis(gfxMemoryLocation aLocation,
|
||||
MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
uint8_t GetPaletteDepth() const { return mPaletteDepth; }
|
||||
uint32_t PaletteDataLength() const {
|
||||
if (!mPaletteDepth)
|
||||
return 0;
|
||||
|
||||
return ((1 << mPaletteDepth) * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
private: // methods
|
||||
|
||||
~imgFrame();
|
||||
@ -190,6 +241,23 @@ private: // methods
|
||||
nsresult LockImageData();
|
||||
nsresult UnlockImageData();
|
||||
nsresult Optimize();
|
||||
nsresult Deoptimize();
|
||||
|
||||
void AssertImageDataLocked() const;
|
||||
|
||||
bool ImageCompleteInternal() const;
|
||||
nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
|
||||
void GetImageDataInternal(uint8_t **aData, uint32_t *length) const;
|
||||
uint32_t GetImageBytesPerRow() const;
|
||||
uint32_t GetImageDataLength() const;
|
||||
int32_t GetStride() const;
|
||||
TemporaryRef<SourceSurface> GetSurfaceInternal();
|
||||
|
||||
uint32_t PaletteDataLength() const
|
||||
{
|
||||
return mPaletteDepth ? (1 << mPaletteDepth) * sizeof(uint32_t)
|
||||
: 0;
|
||||
}
|
||||
|
||||
struct SurfaceWithFormat {
|
||||
nsRefPtr<gfxDrawable> mDrawable;
|
||||
@ -210,46 +278,65 @@ private: // methods
|
||||
SourceSurface* aSurface);
|
||||
|
||||
private: // data
|
||||
friend class DrawableFrameRef;
|
||||
friend class RawAccessFrameRef;
|
||||
friend class UnlockImageDataRunnable;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Thread-safe mutable data, protected by mMutex.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
mutable Mutex mMutex;
|
||||
|
||||
RefPtr<DataSourceSurface> mImageSurface;
|
||||
RefPtr<SourceSurface> mOptSurface;
|
||||
|
||||
RefPtr<VolatileBuffer> mVBuf;
|
||||
VolatileBufferPtr<uint8_t> mVBufPtr;
|
||||
|
||||
nsIntRect mDecoded;
|
||||
|
||||
//! Number of RawAccessFrameRefs currently alive for this imgFrame.
|
||||
int32_t mLockCount;
|
||||
|
||||
//! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.)
|
||||
int32_t mTimeout; // -1 means display forever.
|
||||
|
||||
DisposalMethod mDisposalMethod;
|
||||
BlendMethod mBlendMethod;
|
||||
SurfaceFormat mFormat;
|
||||
|
||||
bool mHasNoAlpha;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Effectively const data, only mutated in the Init methods.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
IntSize mImageSize;
|
||||
IntSize mSize;
|
||||
nsIntPoint mOffset;
|
||||
|
||||
nsIntRect mDecoded;
|
||||
|
||||
mutable Mutex mDecodedMutex;
|
||||
|
||||
// The palette and image data for images that are paletted, since Cairo
|
||||
// doesn't support these images.
|
||||
// The paletted data comes first, then the image data itself.
|
||||
// Total length is PaletteDataLength() + GetImageDataLength().
|
||||
uint8_t* mPalettedImageData;
|
||||
uint8_t mPaletteDepth;
|
||||
|
||||
bool mNonPremult;
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Main-thread-only mutable data.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Note that the data stored in gfx::Color is *non-alpha-premultiplied*.
|
||||
Color mSinglePixelColor;
|
||||
|
||||
int32_t mTimeout; // -1 means display forever
|
||||
|
||||
/** Indicates how many readers currently have locked this frame */
|
||||
int32_t mLockCount;
|
||||
|
||||
RefPtr<VolatileBuffer> mVBuf;
|
||||
VolatileBufferPtr<uint8_t> mVBufPtr;
|
||||
|
||||
SurfaceFormat mFormat;
|
||||
uint8_t mPaletteDepth;
|
||||
DisposalMethod mDisposalMethod;
|
||||
BlendMethod mBlendMethod;
|
||||
bool mSinglePixel;
|
||||
bool mCompositingFailed;
|
||||
bool mHasNoAlpha;
|
||||
bool mNonPremult;
|
||||
bool mOptimizable;
|
||||
|
||||
friend class DrawableFrameRef;
|
||||
friend class RawAccessFrameRef;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,5 +0,0 @@
|
||||
[2d.drawImage.broken.html]
|
||||
type: testharness
|
||||
[Canvas test: 2d.drawImage.broken]
|
||||
expected: FAIL
|
||||
|
Loading…
Reference in New Issue
Block a user