1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "DecodePool.h"
9 #include "GeckoProfiler.h"
10 #include "IDecodingTask.h"
11 #include "ISurfaceProvider.h"
12 #include "mozilla/gfx/2D.h"
13 #include "mozilla/gfx/Point.h"
14 #include "mozilla/Telemetry.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsProxyRelease.h"
17 #include "nsServiceManagerUtils.h"
19 using mozilla::gfx::IntPoint
;
20 using mozilla::gfx::IntSize
;
21 using mozilla::gfx::IntRect
;
22 using mozilla::gfx::SurfaceFormat
;
27 class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
30 explicit AutoRecordDecoderTelemetry(Decoder
* aDecoder
)
35 // Begin recording telemetry data.
36 mStartTime
= TimeStamp::Now();
39 ~AutoRecordDecoderTelemetry()
42 mDecoder
->mDecodeTime
+= (TimeStamp::Now() - mStartTime
);
50 Decoder::Decoder(RasterImage
* aImage
)
56 , mProgress(NoProgress
)
58 , mLoopLength(FrameTimeout::Zero())
59 , mDecoderFlags(DefaultDecoderFlags())
60 , mSurfaceFlags(DefaultSurfaceFlags())
62 , mMetadataDecode(false)
63 , mHaveExplicitOutputSize(false)
65 , mFinishedNewFrame(false)
66 , mHasFrameToTake(false)
67 , mReachedTerminalState(false)
70 , mShouldReportError(false)
71 , mFinalizeFrames(true)
76 MOZ_ASSERT(mProgress
== NoProgress
|| !mImage
,
77 "Destroying Decoder without taking all its progress changes");
78 MOZ_ASSERT(mInvalidRect
.IsEmpty() || !mImage
,
79 "Destroying Decoder without taking all its invalidations");
82 if (mImage
&& !NS_IsMainThread()) {
83 // Dispatch mImage to main thread to prevent it from being destructed by the
85 NS_ReleaseOnMainThreadSystemGroup(mImage
.forget());
90 * Common implementation of the decoder interface.
97 MOZ_ASSERT(!mInitialized
, "Can't re-initialize a decoder!");
99 // All decoders must have a SourceBufferIterator.
100 MOZ_ASSERT(mIterator
);
102 // Metadata decoders must not set an output size.
103 MOZ_ASSERT_IF(mMetadataDecode
, !mHaveExplicitOutputSize
);
105 // All decoders must be anonymous except for metadata decoders.
106 // XXX(seth): Soon that exception will be removed.
107 MOZ_ASSERT_IF(mImage
, IsMetadataDecode());
109 // Implementation-specific initialization.
110 nsresult rv
= InitInternal();
118 Decoder::Decode(IResumable
* aOnResume
/* = nullptr */)
120 MOZ_ASSERT(mInitialized
, "Should be initialized here");
121 MOZ_ASSERT(mIterator
, "Should have a SourceBufferIterator");
123 // If we're already done, don't attempt to keep decoding.
124 if (GetDecodeDone()) {
125 return LexerResult(HasError() ? TerminalState::FAILURE
126 : TerminalState::SUCCESS
);
129 LexerResult
lexerResult(TerminalState::FAILURE
);
131 AUTO_PROFILER_LABEL("Decoder::Decode", GRAPHICS
);
132 AutoRecordDecoderTelemetry
telemetry(this);
134 lexerResult
= DoDecode(*mIterator
, aOnResume
);
137 if (lexerResult
.is
<Yield
>()) {
138 // We either need more data to continue (in which case either @aOnResume or
139 // the caller will reschedule us to run again later), or the decoder is
140 // yielding to allow the caller access to some intermediate output.
144 // We reached a terminal state; we're now done decoding.
145 MOZ_ASSERT(lexerResult
.is
<TerminalState
>());
146 mReachedTerminalState
= true;
148 // If decoding failed, record that fact.
149 if (lexerResult
.as
<TerminalState
>() == TerminalState::FAILURE
) {
153 // Perform final cleanup.
156 return LexerResult(HasError() ? TerminalState::FAILURE
157 : TerminalState::SUCCESS
);
161 Decoder::TerminateFailure()
165 // Perform final cleanup if need be.
166 if (!mReachedTerminalState
) {
167 mReachedTerminalState
= true;
171 return LexerResult(TerminalState::FAILURE
);
175 Decoder::ShouldSyncDecode(size_t aByteLimit
)
177 MOZ_ASSERT(aByteLimit
> 0);
178 MOZ_ASSERT(mIterator
, "Should have a SourceBufferIterator");
180 return mIterator
->RemainingBytesIsNoMoreThan(aByteLimit
);
184 Decoder::CompleteDecode()
186 // Implementation-specific finalization.
187 nsresult rv
= BeforeFinishInternal();
192 rv
= HasError() ? FinishWithErrorInternal()
198 if (IsMetadataDecode()) {
199 // If this was a metadata decode and we never got a size, the decode failed.
206 // If the implementation left us mid-frame, finish that up. Note that it may
207 // have left us transparent.
209 PostHasTransparency();
213 // If PostDecodeDone() has not been called, we may need to send teardown
214 // notifications if it is unrecoverable.
216 // We should always report an error to the console in this case.
217 mShouldReportError
= true;
219 if (GetCompleteFrameCount() > 0) {
220 // We're usable if we have at least one complete frame, so do exactly
221 // what we should have when the decoder completed.
222 PostHasTransparency();
225 // We're not usable. Record some final progress indicating the error.
226 mProgress
|= FLAG_DECODE_COMPLETE
| FLAG_HAS_ERROR
;
231 MOZ_ASSERT(HasError() || mCurrentFrame
, "Should have an error or a frame");
233 // If this image wasn't animated and isn't a transient image, mark its frame
234 // as optimizable. We don't support optimizing animated images and
235 // optimizing transient images isn't worth it.
236 if (!HasAnimation() &&
237 !(mDecoderFlags
& DecoderFlags::IMAGE_IS_TRANSIENT
) &&
239 mCurrentFrame
->SetOptimizable();
245 Decoder::SetOutputSize(const gfx::IntSize
& aSize
)
247 mOutputSize
= Some(aSize
);
248 mHaveExplicitOutputSize
= true;
252 Decoder::ExplicitOutputSize() const
254 MOZ_ASSERT_IF(mHaveExplicitOutputSize
, mOutputSize
);
255 return mHaveExplicitOutputSize
? mOutputSize
: Nothing();
259 Decoder::TakeCompleteFrameCount()
261 const bool finishedNewFrame
= mFinishedNewFrame
;
262 mFinishedNewFrame
= false;
263 return finishedNewFrame
? Some(GetCompleteFrameCount()) : Nothing();
267 Decoder::FinalStatus() const
269 return DecoderFinalStatus(IsMetadataDecode(),
272 ShouldReportError());
276 Decoder::Telemetry() const
278 MOZ_ASSERT(mIterator
);
279 return DecoderTelemetry(SpeedHistogram(),
280 mIterator
->ByteCount(),
281 mIterator
->ChunkCount(),
286 Decoder::AllocateFrame(uint32_t aFrameNum
,
287 const gfx::IntSize
& aOutputSize
,
288 const gfx::IntRect
& aFrameRect
,
289 gfx::SurfaceFormat aFormat
,
290 uint8_t aPaletteDepth
)
292 mCurrentFrame
= AllocateFrameInternal(aFrameNum
, aOutputSize
, aFrameRect
,
293 aFormat
, aPaletteDepth
,
294 mCurrentFrame
.get());
297 mHasFrameToTake
= true;
299 // Gather the raw pointers the decoders will use.
300 mCurrentFrame
->GetImageData(&mImageData
, &mImageDataLength
);
301 mCurrentFrame
->GetPaletteData(&mColormap
, &mColormapSize
);
303 // We should now be on |aFrameNum|. (Note that we're comparing the frame
304 // number, which is zero-based, with the frame count, which is one-based.)
305 MOZ_ASSERT(aFrameNum
+ 1 == mFrameCount
);
307 // If we're past the first frame, PostIsAnimated() should've been called.
308 MOZ_ASSERT_IF(mFrameCount
> 1, HasAnimation());
310 // Update our state to reflect the new frame.
311 MOZ_ASSERT(!mInFrame
, "Starting new frame but not done with old one!");
315 return mCurrentFrame
? NS_OK
: NS_ERROR_FAILURE
;
319 Decoder::AllocateFrameInternal(uint32_t aFrameNum
,
320 const gfx::IntSize
& aOutputSize
,
321 const gfx::IntRect
& aFrameRect
,
322 SurfaceFormat aFormat
,
323 uint8_t aPaletteDepth
,
324 imgFrame
* aPreviousFrame
)
327 return RawAccessFrameRef();
330 if (aFrameNum
!= mFrameCount
) {
331 MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
332 return RawAccessFrameRef();
335 if (aOutputSize
.width
<= 0 || aOutputSize
.height
<= 0 ||
336 aFrameRect
.Width() <= 0 || aFrameRect
.Height() <= 0) {
337 NS_WARNING("Trying to add frame with zero or negative size");
338 return RawAccessFrameRef();
341 auto frame
= MakeNotNull
<RefPtr
<imgFrame
>>();
342 bool nonPremult
= bool(mSurfaceFlags
& SurfaceFlags::NO_PREMULTIPLY_ALPHA
);
343 if (NS_FAILED(frame
->InitForDecoder(aOutputSize
, aFrameRect
, aFormat
,
344 aPaletteDepth
, nonPremult
,
346 NS_WARNING("imgFrame::Init should succeed");
347 return RawAccessFrameRef();
350 RawAccessFrameRef ref
= frame
->RawAccessRef();
353 return RawAccessFrameRef();
356 if (aFrameNum
== 1) {
357 MOZ_ASSERT(aPreviousFrame
, "Must provide a previous frame when animated");
358 aPreviousFrame
->SetRawAccessOnly();
360 // If we dispose of the first frame by clearing it, then the first frame's
361 // refresh area is all of itself.
362 // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
363 AnimationData previousFrameData
= aPreviousFrame
->GetAnimationData();
364 if (previousFrameData
.mDisposalMethod
== DisposalMethod::CLEAR
||
365 previousFrameData
.mDisposalMethod
== DisposalMethod::CLEAR_ALL
||
366 previousFrameData
.mDisposalMethod
== DisposalMethod::RESTORE_PREVIOUS
) {
367 mFirstFrameRefreshArea
= previousFrameData
.mRect
;
372 ref
->SetRawAccessOnly();
374 // Some GIFs are huge but only have a small area that they animate. We only
375 // need to refresh that small area when frame 0 comes around again.
376 mFirstFrameRefreshArea
.UnionRect(mFirstFrameRefreshArea
, frame
->GetRect());
385 * Hook stubs. Override these as necessary in decoder implementations.
388 nsresult
Decoder::InitInternal() { return NS_OK
; }
389 nsresult
Decoder::BeforeFinishInternal() { return NS_OK
; }
390 nsresult
Decoder::FinishInternal() { return NS_OK
; }
392 nsresult
Decoder::FinishWithErrorInternal()
394 MOZ_ASSERT(!mInFrame
);
399 * Progress Notifications
403 Decoder::PostSize(int32_t aWidth
,
405 Orientation aOrientation
/* = Orientation()*/)
408 MOZ_ASSERT(aWidth
>= 0, "Width can't be negative!");
409 MOZ_ASSERT(aHeight
>= 0, "Height can't be negative!");
411 // Set our intrinsic size.
412 mImageMetadata
.SetSize(aWidth
, aHeight
, aOrientation
);
414 // Verify it is the expected size, if given. Note that this is only used by
415 // the ICO decoder for embedded image types, so only its subdecoders are
416 // required to handle failures in PostSize.
417 if (!IsExpectedSize()) {
422 // Set our output size if it's not already set.
424 mOutputSize
= Some(IntSize(aWidth
, aHeight
));
427 MOZ_ASSERT(mOutputSize
->width
<= aWidth
&& mOutputSize
->height
<= aHeight
,
428 "Output size will result in upscaling");
430 // Create a downscaler if we need to downscale. This is used by legacy
431 // decoders that haven't been converted to use SurfacePipe yet.
432 // XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
433 if (mOutputSize
->width
< aWidth
|| mOutputSize
->height
< aHeight
) {
434 mDownscaler
.emplace(*mOutputSize
);
437 // Record this notification.
438 mProgress
|= FLAG_SIZE_AVAILABLE
;
442 Decoder::PostHasTransparency()
444 mProgress
|= FLAG_HAS_TRANSPARENCY
;
448 Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout
)
450 mProgress
|= FLAG_IS_ANIMATED
;
451 mImageMetadata
.SetHasAnimation();
452 mImageMetadata
.SetFirstFrameTimeout(aFirstFrameTimeout
);
456 Decoder::PostFrameStop(Opacity aFrameOpacity
457 /* = Opacity::SOME_TRANSPARENCY */,
458 DisposalMethod aDisposalMethod
459 /* = DisposalMethod::KEEP */,
460 FrameTimeout aTimeout
/* = FrameTimeout::Forever() */,
461 BlendMethod aBlendMethod
/* = BlendMethod::OVER */,
462 const Maybe
<nsIntRect
>& aBlendRect
/* = Nothing() */)
464 // We should be mid-frame
465 MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
466 MOZ_ASSERT(mInFrame
, "Stopping frame when we didn't start one");
467 MOZ_ASSERT(mCurrentFrame
, "Stopping frame when we don't have one");
471 mFinishedNewFrame
= true;
473 mCurrentFrame
->Finish(aFrameOpacity
, aDisposalMethod
, aTimeout
,
474 aBlendMethod
, aBlendRect
, mFinalizeFrames
);
476 mProgress
|= FLAG_FRAME_COMPLETE
;
478 mLoopLength
+= aTimeout
;
480 // If we're not sending partial invalidations, then we send an invalidation
481 // here when the first frame is complete.
482 if (!ShouldSendPartialInvalidations() && mFrameCount
== 1) {
483 mInvalidRect
.UnionRect(mInvalidRect
,
484 IntRect(IntPoint(), Size()));
489 Decoder::PostInvalidation(const gfx::IntRect
& aRect
,
490 const Maybe
<gfx::IntRect
>& aRectAtOutputSize
493 // We should be mid-frame
494 MOZ_ASSERT(mInFrame
, "Can't invalidate when not mid-frame!");
495 MOZ_ASSERT(mCurrentFrame
, "Can't invalidate when not mid-frame!");
497 // Record this invalidation, unless we're not sending partial invalidations
498 // or we're past the first frame.
499 if (ShouldSendPartialInvalidations() && mFrameCount
== 1) {
500 mInvalidRect
.UnionRect(mInvalidRect
, aRect
);
501 mCurrentFrame
->ImageUpdated(aRectAtOutputSize
.valueOr(aRect
));
506 Decoder::PostDecodeDone(int32_t aLoopCount
/* = 0 */)
508 MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
509 MOZ_ASSERT(!mInFrame
, "Can't be done decoding if we're mid-frame!");
510 MOZ_ASSERT(!mDecodeDone
, "Decode already done!");
513 mImageMetadata
.SetLoopCount(aLoopCount
);
515 // Some metadata that we track should take into account every frame in the
516 // image. If this is a first-frame-only decode, our accumulated loop length
517 // and first frame refresh area only includes the first frame, so it's not
518 // correct and we don't record it.
519 if (!IsFirstFrameDecode()) {
520 mImageMetadata
.SetLoopLength(mLoopLength
);
521 mImageMetadata
.SetFirstFrameRefreshArea(mFirstFrameRefreshArea
);
524 mProgress
|= FLAG_DECODE_COMPLETE
;
533 MOZ_ASSERT(mCurrentFrame
);
534 MOZ_ASSERT(mFrameCount
> 0);
535 mCurrentFrame
->Abort();
538 mHasFrameToTake
= false;
543 } // namespace mozilla