123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- /* -*- 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 "Decoder.h"
- #include "mozilla/gfx/2D.h"
- #include "DecodePool.h"
- #include "GeckoProfiler.h"
- #include "IDecodingTask.h"
- #include "ISurfaceProvider.h"
- #include "nsProxyRelease.h"
- #include "nsServiceManagerUtils.h"
- #include "nsComponentManagerUtils.h"
- #include "mozilla/Telemetry.h"
- using mozilla::gfx::IntSize;
- using mozilla::gfx::SurfaceFormat;
- namespace mozilla {
- namespace image {
- class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
- {
- public:
- explicit AutoRecordDecoderTelemetry(Decoder* aDecoder)
- : mDecoder(aDecoder)
- {
- MOZ_ASSERT(mDecoder);
- // Begin recording telemetry data.
- mStartTime = TimeStamp::Now();
- }
- ~AutoRecordDecoderTelemetry()
- {
- // Finish telemetry.
- mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
- }
- private:
- Decoder* mDecoder;
- TimeStamp mStartTime;
- };
- Decoder::Decoder(RasterImage* aImage)
- : mImageData(nullptr)
- , mImageDataLength(0)
- , mColormap(nullptr)
- , mColormapSize(0)
- , mImage(aImage)
- , mProgress(NoProgress)
- , mFrameCount(0)
- , mLoopLength(FrameTimeout::Zero())
- , mDecoderFlags(DefaultDecoderFlags())
- , mSurfaceFlags(DefaultSurfaceFlags())
- , mInitialized(false)
- , mMetadataDecode(false)
- , mHaveExplicitOutputSize(false)
- , mInFrame(false)
- , mFinishedNewFrame(false)
- , mReachedTerminalState(false)
- , mDecodeDone(false)
- , mError(false)
- , mShouldReportError(false)
- { }
- Decoder::~Decoder()
- {
- MOZ_ASSERT(mProgress == NoProgress || !mImage,
- "Destroying Decoder without taking all its progress changes");
- MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
- "Destroying Decoder without taking all its invalidations");
- mInitialized = false;
- if (mImage && !NS_IsMainThread()) {
- // Dispatch mImage to main thread to prevent it from being destructed by the
- // decode thread.
- NS_ReleaseOnMainThread(mImage.forget());
- }
- }
- /*
- * Common implementation of the decoder interface.
- */
- nsresult
- Decoder::Init()
- {
- // No re-initializing
- MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
- // All decoders must have a SourceBufferIterator.
- MOZ_ASSERT(mIterator);
- // Metadata decoders must not set an output size.
- MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
- // All decoders must be anonymous except for metadata decoders.
- // XXX(seth): Soon that exception will be removed.
- MOZ_ASSERT_IF(mImage, IsMetadataDecode());
- // Implementation-specific initialization.
- nsresult rv = InitInternal();
- mInitialized = true;
- return rv;
- }
- LexerResult
- Decoder::Decode(IResumable* aOnResume /* = nullptr */)
- {
- MOZ_ASSERT(mInitialized, "Should be initialized here");
- MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
- // If we're already done, don't attempt to keep decoding.
- if (GetDecodeDone()) {
- return LexerResult(HasError() ? TerminalState::FAILURE
- : TerminalState::SUCCESS);
- }
- LexerResult lexerResult(TerminalState::FAILURE);
- {
- PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS);
- AutoRecordDecoderTelemetry telemetry(this);
- lexerResult = DoDecode(*mIterator, aOnResume);
- };
- if (lexerResult.is<Yield>()) {
- // We either need more data to continue (in which case either @aOnResume or
- // the caller will reschedule us to run again later), or the decoder is
- // yielding to allow the caller access to some intermediate output.
- return lexerResult;
- }
- // We reached a terminal state; we're now done decoding.
- MOZ_ASSERT(lexerResult.is<TerminalState>());
- mReachedTerminalState = true;
- // If decoding failed, record that fact.
- if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
- PostError();
- }
- // Perform final cleanup.
- CompleteDecode();
- return LexerResult(HasError() ? TerminalState::FAILURE
- : TerminalState::SUCCESS);
- }
- LexerResult
- Decoder::TerminateFailure()
- {
- PostError();
- // Perform final cleanup if need be.
- if (!mReachedTerminalState) {
- mReachedTerminalState = true;
- CompleteDecode();
- }
- return LexerResult(TerminalState::FAILURE);
- }
- bool
- Decoder::ShouldSyncDecode(size_t aByteLimit)
- {
- MOZ_ASSERT(aByteLimit > 0);
- MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
- return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
- }
- void
- Decoder::CompleteDecode()
- {
- // Implementation-specific finalization.
- nsresult rv = BeforeFinishInternal();
- if (NS_FAILED(rv)) {
- PostError();
- }
- rv = HasError() ? FinishWithErrorInternal()
- : FinishInternal();
- if (NS_FAILED(rv)) {
- PostError();
- }
- if (IsMetadataDecode()) {
- // If this was a metadata decode and we never got a size, the decode failed.
- if (!HasSize()) {
- PostError();
- }
- return;
- }
- // If the implementation left us mid-frame, finish that up. Note that it may
- // have left us transparent.
- if (mInFrame) {
- PostHasTransparency();
- PostFrameStop();
- }
- // If PostDecodeDone() has not been called, we may need to send teardown
- // notifications if it is unrecoverable.
- if (!mDecodeDone) {
- // We should always report an error to the console in this case.
- mShouldReportError = true;
- if (GetCompleteFrameCount() > 0) {
- // We're usable if we have at least one complete frame, so do exactly
- // what we should have when the decoder completed.
- PostHasTransparency();
- PostDecodeDone();
- } else {
- // We're not usable. Record some final progress indicating the error.
- mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
- }
- }
- if (mDecodeDone) {
- MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
- // If this image wasn't animated and isn't a transient image, mark its frame
- // as optimizable. We don't support optimizing animated images and
- // optimizing transient images isn't worth it.
- if (!HasAnimation() &&
- !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
- mCurrentFrame) {
- mCurrentFrame->SetOptimizable();
- }
- }
- }
- void
- Decoder::SetOutputSize(const gfx::IntSize& aSize)
- {
- mOutputSize = Some(aSize);
- mHaveExplicitOutputSize = true;
- }
- Maybe<gfx::IntSize>
- Decoder::ExplicitOutputSize() const
- {
- MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
- return mHaveExplicitOutputSize ? mOutputSize : Nothing();
- }
- Maybe<uint32_t>
- Decoder::TakeCompleteFrameCount()
- {
- const bool finishedNewFrame = mFinishedNewFrame;
- mFinishedNewFrame = false;
- return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
- }
- DecoderFinalStatus
- Decoder::FinalStatus() const
- {
- return DecoderFinalStatus(IsMetadataDecode(),
- GetDecodeDone(),
- HasError(),
- ShouldReportError());
- }
- DecoderTelemetry
- Decoder::Telemetry() const
- {
- MOZ_ASSERT(mIterator);
- return DecoderTelemetry(SpeedHistogram(),
- mIterator->ByteCount(),
- mIterator->ChunkCount(),
- mDecodeTime);
- }
- nsresult
- Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
- const gfx::IntRect& aFrameRect,
- gfx::SurfaceFormat aFormat,
- uint8_t aPaletteDepth,
- const Maybe<AnimationParams>& aAnimParams)
- {
- mCurrentFrame = AllocateFrameInternal(aOutputSize, aFrameRect, aFormat,
- aPaletteDepth, aAnimParams,
- mCurrentFrame.get());
- if (mCurrentFrame) {
- // Gather the raw pointers the decoders will use.
- mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
- mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
- // We should now be on |aFrameNum|. (Note that we're comparing the frame
- // number, which is zero-based, with the frame count, which is one-based.)
- MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount);
- // If we're past the first frame, PostIsAnimated() should've been called.
- MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
- // Update our state to reflect the new frame.
- MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
- mInFrame = true;
- }
- return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
- }
- RawAccessFrameRef
- Decoder::AllocateFrameInternal(const gfx::IntSize& aOutputSize,
- const gfx::IntRect& aFrameRect,
- SurfaceFormat aFormat,
- uint8_t aPaletteDepth,
- const Maybe<AnimationParams>& aAnimParams,
- imgFrame* aPreviousFrame)
- {
- if (HasError()) {
- return RawAccessFrameRef();
- }
- uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0;
- if (frameNum != mFrameCount) {
- MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
- return RawAccessFrameRef();
- }
- if (aOutputSize.width <= 0 || aOutputSize.height <= 0 ||
- aFrameRect.width <= 0 || aFrameRect.height <= 0) {
- NS_WARNING("Trying to add frame with zero or negative size");
- return RawAccessFrameRef();
- }
- NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
- bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
- if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
- aPaletteDepth, nonPremult,
- aAnimParams))) {
- NS_WARNING("imgFrame::Init should succeed");
- return RawAccessFrameRef();
- }
- RawAccessFrameRef ref = frame->RawAccessRef();
- if (!ref) {
- frame->Abort();
- return RawAccessFrameRef();
- }
- if (frameNum == 1) {
- MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
- aPreviousFrame->SetRawAccessOnly();
- // 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 prevDisposal = aPreviousFrame->GetDisposalMethod();
- if (prevDisposal == DisposalMethod::CLEAR ||
- prevDisposal == DisposalMethod::CLEAR_ALL ||
- prevDisposal == DisposalMethod::RESTORE_PREVIOUS) {
- mFirstFrameRefreshArea = aPreviousFrame->GetRect();
- }
- }
- if (frameNum > 0) {
- ref->SetRawAccessOnly();
- // Some GIFs are huge but only have a small area that they animate. We only
- // need to refresh that small area when frame 0 comes around again.
- mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, frame->GetRect());
- }
- mFrameCount++;
- return ref;
- }
- /*
- * Hook stubs. Override these as necessary in decoder implementations.
- */
- nsresult Decoder::InitInternal() { return NS_OK; }
- nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
- nsresult Decoder::FinishInternal() { return NS_OK; }
- nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }
- /*
- * Progress Notifications
- */
- void
- Decoder::PostSize(int32_t aWidth,
- int32_t aHeight,
- Orientation aOrientation /* = Orientation()*/)
- {
- // Validate.
- MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
- MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");
- // Set our intrinsic size.
- mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
- // Set our output size if it's not already set.
- if (!mOutputSize) {
- mOutputSize = Some(IntSize(aWidth, aHeight));
- }
- MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight,
- "Output size will result in upscaling");
- // Create a downscaler if we need to downscale. This is used by legacy
- // decoders that haven't been converted to use SurfacePipe yet.
- // XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
- if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) {
- mDownscaler.emplace(*mOutputSize);
- }
- // Record this notification.
- mProgress |= FLAG_SIZE_AVAILABLE;
- }
- void
- Decoder::PostHasTransparency()
- {
- mProgress |= FLAG_HAS_TRANSPARENCY;
- }
- void
- Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout)
- {
- mProgress |= FLAG_IS_ANIMATED;
- mImageMetadata.SetHasAnimation();
- mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
- }
- void
- Decoder::PostFrameStop(Opacity aFrameOpacity)
- {
- // We should be mid-frame
- MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
- MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
- MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
- // Update our state.
- mInFrame = false;
- mFinishedNewFrame = true;
- mCurrentFrame->Finish(aFrameOpacity);
- mProgress |= FLAG_FRAME_COMPLETE;
- mLoopLength += mCurrentFrame->GetTimeout();
- // If we're not sending partial invalidations, then we send an invalidation
- // here when the first frame is complete.
- if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
- mInvalidRect.UnionRect(mInvalidRect,
- IntRect(IntPoint(), Size()));
- }
- }
- void
- Decoder::PostInvalidation(const gfx::IntRect& aRect,
- const Maybe<gfx::IntRect>& aRectAtOutputSize
- /* = Nothing() */)
- {
- // We should be mid-frame
- MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
- MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
- // Record this invalidation, unless we're not sending partial invalidations
- // or we're past the first frame.
- if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
- mInvalidRect.UnionRect(mInvalidRect, aRect);
- mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect));
- }
- }
- void
- Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
- {
- MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
- MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
- MOZ_ASSERT(!mDecodeDone, "Decode already done!");
- mDecodeDone = true;
- mImageMetadata.SetLoopCount(aLoopCount);
- // Some metadata that we track should take into account every frame in the
- // image. If this is a first-frame-only decode, our accumulated loop length
- // and first frame refresh area only includes the first frame, so it's not
- // correct and we don't record it.
- if (!IsFirstFrameDecode()) {
- mImageMetadata.SetLoopLength(mLoopLength);
- mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
- }
- mProgress |= FLAG_DECODE_COMPLETE;
- }
- void
- Decoder::PostError()
- {
- mError = true;
- if (mInFrame) {
- MOZ_ASSERT(mCurrentFrame);
- MOZ_ASSERT(mFrameCount > 0);
- mCurrentFrame->Abort();
- mInFrame = false;
- --mFrameCount;
- }
- }
- } // namespace image
- } // namespace mozilla
|