Bug 1287691 (Part 3) - Yield after each frame in the PNG decoder. r=edwin

This commit is contained in:
Seth Fowler 2016-07-18 23:51:16 -07:00
parent 6d790d3506
commit 62bf80f260
2 changed files with 106 additions and 32 deletions

View File

@ -26,6 +26,8 @@
using namespace mozilla::gfx;
using std::min;
namespace mozilla {
namespace image {
@ -100,6 +102,8 @@ nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
State::PNG_DATA,
SIZE_MAX),
Transition::TerminateSuccess())
, mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA))
, mLastChunkLength(0)
, mPNG(nullptr)
, mInfo(nullptr)
, mCMSLine(nullptr)
@ -177,15 +181,13 @@ nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType)
// CreateFrame() is used for both simple and animated images.
nsresult
nsPNGDecoder::CreateFrame(SurfaceFormat aFormat,
const IntRect& aFrameRect,
bool aIsInterlaced)
nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
{
MOZ_ASSERT(HasSize());
MOZ_ASSERT(!IsMetadataDecode());
// Check if we have transparency, and send notifications if needed.
auto transparency = GetTransparencyType(aFormat, aFrameRect);
auto transparency = GetTransparencyType(aFrameInfo.mFormat, aFrameInfo.mFrameRect);
PostHasTransparencyIfNeeded(transparency);
SurfaceFormat format = transparency == TransparencyType::eNone
? SurfaceFormat::B8G8R8X8
@ -201,7 +203,7 @@ nsPNGDecoder::CreateFrame(SurfaceFormat aFormat,
// If this image is interlaced, we can display better quality intermediate
// results to the user by post processing them with ADAM7InterpolatingFilter.
SurfacePipeFlags pipeFlags = aIsInterlaced
SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced
? SurfacePipeFlags::ADAM7_INTERPOLATE
: SurfacePipeFlags();
@ -211,8 +213,9 @@ nsPNGDecoder::CreateFrame(SurfaceFormat aFormat,
}
Maybe<SurfacePipe> pipe =
SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, GetSize(), targetSize,
aFrameRect, format, pipeFlags);
SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, GetSize(),
targetSize, aFrameInfo.mFrameRect,
format, pipeFlags);
if (!pipe) {
mPipe = SurfacePipe();
@ -221,13 +224,13 @@ nsPNGDecoder::CreateFrame(SurfaceFormat aFormat,
mPipe = Move(*pipe);
mFrameRect = aFrameRect;
mFrameRect = aFrameInfo.mFrameRect;
mPass = 0;
MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug,
("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
"image frame with %dx%d pixels for decoder %p",
aFrameRect.width, aFrameRect.height, this));
mFrameRect.width, mFrameRect.height, this));
#ifdef PNG_APNG_SUPPORTED
if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
@ -368,26 +371,37 @@ nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
LexerTransition<nsPNGDecoder::State>
nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength)
{
// If we were waiting until after returning from a yield to call
// CreateFrame(), call it now.
if (mNextFrameInfo) {
if (NS_FAILED(CreateFrame(*mNextFrameInfo))) {
return Transition::TerminateFailure();
}
MOZ_ASSERT(mImageData, "Should have a buffer now");
mNextFrameInfo = Nothing();
}
// libpng uses setjmp/longjmp for error handling.
if (setjmp(png_jmpbuf(mPNG))) {
return Transition::TerminateFailure();
}
// Pass the data off to libpng.
mLastChunkLength = aLength;
mNextTransition = Transition::ContinueUnbuffered(State::PNG_DATA);
png_process_data(mPNG, mInfo,
reinterpret_cast<unsigned char*>(const_cast<char*>((aData))),
aLength);
if (HasError()) {
return Transition::TerminateFailure();
}
// Make sure that we've reached a terminal state if decoding is done.
MOZ_ASSERT_IF(GetDecodeDone(), mNextTransition.NextStateIsTerminal());
MOZ_ASSERT_IF(HasError(), mNextTransition.NextStateIsTerminal());
if (GetDecodeDone()) {
return Transition::TerminateSuccess();
}
// Keep reading data.
return Transition::ContinueUnbuffered(State::PNG_DATA);
// Continue with whatever transition the callback code requested. We
// initialized this to Transition::ContinueUnbuffered(State::PNG_DATA) above,
// so by default we just continue the unbuffered read.
return mNextTransition;
}
LexerTransition<nsPNGDecoder::State>
@ -683,8 +697,7 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
// We have the metadata we're looking for, so stop here, before we allocate
// buffers below.
png_process_data_pause(png_ptr, /* save = */ false);
return;
return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
}
#ifdef PNG_APNG_SUPPORTED
@ -697,7 +710,9 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
decoder->mFrameIsHidden = true;
} else {
#endif
nsresult rv = decoder->CreateFrame(decoder->format, frameRect, isInterlaced);
nsresult rv = decoder->CreateFrame(FrameInfo{ decoder->format,
frameRect,
isInterlaced });
if (NS_FAILED(rv)) {
png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
}
@ -899,6 +914,37 @@ nsPNGDecoder::WriteRow(uint8_t* aRow)
PostInvalidationIfNeeded();
}
void
nsPNGDecoder::DoTerminate(png_structp aPNGStruct, TerminalState aState)
{
// Stop processing data. Note that we intentionally ignore the return value of
// png_process_data_pause(), which tells us how many bytes of the data that
// was passed to png_process_data() have not been consumed yet, because now
// that we've reached a terminal state, we won't do any more decoding or call
// back into libpng anymore.
png_process_data_pause(aPNGStruct, /* save = */ false);
mNextTransition = aState == TerminalState::SUCCESS
? Transition::TerminateSuccess()
: Transition::TerminateFailure();
}
void
nsPNGDecoder::DoYield(png_structp aPNGStruct)
{
// Pause data processing. png_process_data_pause() returns how many bytes of
// the data that was passed to png_process_data() have not been consumed yet.
// We use this information to tell StreamingLexer where to place us in the
// input stream when we come back from the yield.
png_size_t pendingBytes = png_process_data_pause(aPNGStruct, /* save = */ false);
MOZ_ASSERT(pendingBytes < mLastChunkLength);
size_t consumedBytes = mLastChunkLength - min(pendingBytes, mLastChunkLength);
mNextTransition =
Transition::ContinueUnbufferedAfterYield(State::PNG_DATA, consumedBytes);
}
#ifdef PNG_APNG_SUPPORTED
// got the header of a new frame that's coming
void
@ -914,13 +960,14 @@ nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
// We're about to get a second non-hidden frame, but we only want the first.
// Stop decoding now. (And avoid allocating the unnecessary buffers below.)
decoder->PostDecodeDone();
png_process_data_pause(png_ptr, /* save = */ false);
return;
return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
}
// Only the first frame can be hidden, so unhide unconditionally here.
decoder->mFrameIsHidden = false;
// Save the information necessary to create the frame; we'll actually create
// it when we return from the yield.
const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo),
png_get_next_frame_y_offset(png_ptr, decoder->mInfo),
png_get_next_frame_width(png_ptr, decoder->mInfo),
@ -928,11 +975,12 @@ nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
const bool isInterlaced = bool(decoder->interlacebuf);
nsresult rv = decoder->CreateFrame(decoder->format, frameRect, isInterlaced);
if (NS_FAILED(rv)) {
png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
}
MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
decoder->mNextFrameInfo = Some(FrameInfo{ decoder->format,
frameRect,
isInterlaced });
// Yield to the caller to notify them that the previous frame is now complete.
return decoder->DoYield(png_ptr);
}
#endif
@ -965,9 +1013,10 @@ nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
}
#endif
// Send final notifications
// Send final notifications.
decoder->EndImageFrame();
decoder->PostDecodeDone(loop_count);
return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
}

View File

@ -36,9 +36,15 @@ private:
// Decoders should only be instantiated via DecoderFactory.
explicit nsPNGDecoder(RasterImage* aImage);
nsresult CreateFrame(gfx::SurfaceFormat aFormat,
const gfx::IntRect& aFrameRect,
bool aIsInterlaced);
/// The information necessary to create a frame.
struct FrameInfo
{
gfx::SurfaceFormat mFormat;
gfx::IntRect mFrameRect;
bool mIsInterlaced;
};
nsresult CreateFrame(const FrameInfo& aFrameInfo);
void EndImageFrame();
enum class TransparencyType
@ -56,6 +62,11 @@ private:
void WriteRow(uint8_t* aRow);
// Convenience methods to make interacting with StreamingLexer from inside
// a libpng callback easier.
void DoTerminate(png_structp aPNGStruct, TerminalState aState);
void DoYield(png_structp aPNGStruct);
enum class State
{
PNG_DATA,
@ -67,6 +78,20 @@ private:
StreamingLexer<State> mLexer;
// The next lexer state transition. We need to store it here because we can't
// directly return arbitrary values from libpng callbacks.
LexerTransition<State> mNextTransition;
// We yield to the caller every time we finish decoding a frame. When this
// happens, we need to allocate the next frame after returning from the yield.
// |mNextFrameInfo| is used to store the information needed to allocate the
// next frame.
Maybe<FrameInfo> mNextFrameInfo;
// The length of the last chunk of data passed to ReadPNGData(). We use this
// to arrange to arrive back at the correct spot in the data after yielding.
size_t mLastChunkLength;
public:
png_structp mPNG;
png_infop mInfo;