Bug 1567650 [wpt PR 17950] - [ElementTiming] Replace responseEnd with loadTime, a...
[gecko.git] / image / Decoder.cpp
blobaf72265aac0c5116497a2aca999796c152e2249d
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/. */
6 #include "Decoder.h"
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::IntRect;
21 using mozilla::gfx::IntSize;
22 using mozilla::gfx::SurfaceFormat;
24 namespace mozilla {
25 namespace image {
27 class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final {
28 public:
29 explicit AutoRecordDecoderTelemetry(Decoder* aDecoder) : mDecoder(aDecoder) {
30 MOZ_ASSERT(mDecoder);
32 // Begin recording telemetry data.
33 mStartTime = TimeStamp::Now();
36 ~AutoRecordDecoderTelemetry() {
37 // Finish telemetry.
38 mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
41 private:
42 Decoder* mDecoder;
43 TimeStamp mStartTime;
46 Decoder::Decoder(RasterImage* aImage)
47 : mInProfile(nullptr),
48 mTransform(nullptr),
49 mImageData(nullptr),
50 mImageDataLength(0),
51 mImage(aImage),
52 mFrameRecycler(nullptr),
53 mProgress(NoProgress),
54 mFrameCount(0),
55 mLoopLength(FrameTimeout::Zero()),
56 mDecoderFlags(DefaultDecoderFlags()),
57 mSurfaceFlags(DefaultSurfaceFlags()),
58 mInitialized(false),
59 mMetadataDecode(false),
60 mHaveExplicitOutputSize(false),
61 mInFrame(false),
62 mFinishedNewFrame(false),
63 mHasFrameToTake(false),
64 mReachedTerminalState(false),
65 mDecodeDone(false),
66 mError(false),
67 mShouldReportError(false),
68 mFinalizeFrames(true) {}
70 Decoder::~Decoder() {
71 MOZ_ASSERT(mProgress == NoProgress || !mImage,
72 "Destroying Decoder without taking all its progress changes");
73 MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
74 "Destroying Decoder without taking all its invalidations");
75 mInitialized = false;
77 if (mInProfile) {
78 // mTransform belongs to us only if mInProfile is non-null
79 if (mTransform) {
80 qcms_transform_release(mTransform);
82 qcms_profile_release(mInProfile);
85 if (mImage && !NS_IsMainThread()) {
86 // Dispatch mImage to main thread to prevent it from being destructed by the
87 // decode thread.
88 NS_ReleaseOnMainThreadSystemGroup(mImage.forget());
93 * Common implementation of the decoder interface.
96 nsresult Decoder::Init() {
97 // No re-initializing
98 MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
100 // All decoders must have a SourceBufferIterator.
101 MOZ_ASSERT(mIterator);
103 // Metadata decoders must not set an output size.
104 MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
106 // All decoders must be anonymous except for metadata decoders.
107 // XXX(seth): Soon that exception will be removed.
108 MOZ_ASSERT_IF(mImage, IsMetadataDecode());
110 // Implementation-specific initialization.
111 nsresult rv = InitInternal();
113 mInitialized = true;
115 return rv;
118 LexerResult Decoder::Decode(IResumable* aOnResume /* = nullptr */) {
119 MOZ_ASSERT(mInitialized, "Should be initialized here");
120 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
122 // If we're already done, don't attempt to keep decoding.
123 if (GetDecodeDone()) {
124 return LexerResult(HasError() ? TerminalState::FAILURE
125 : TerminalState::SUCCESS);
128 LexerResult lexerResult(TerminalState::FAILURE);
130 AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_ImageDecoding);
131 AutoRecordDecoderTelemetry telemetry(this);
133 lexerResult = DoDecode(*mIterator, aOnResume);
136 if (lexerResult.is<Yield>()) {
137 // We either need more data to continue (in which case either @aOnResume or
138 // the caller will reschedule us to run again later), or the decoder is
139 // yielding to allow the caller access to some intermediate output.
140 return lexerResult;
143 // We reached a terminal state; we're now done decoding.
144 MOZ_ASSERT(lexerResult.is<TerminalState>());
145 mReachedTerminalState = true;
147 // If decoding failed, record that fact.
148 if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
149 PostError();
152 // Perform final cleanup.
153 CompleteDecode();
155 return LexerResult(HasError() ? TerminalState::FAILURE
156 : TerminalState::SUCCESS);
159 LexerResult Decoder::TerminateFailure() {
160 PostError();
162 // Perform final cleanup if need be.
163 if (!mReachedTerminalState) {
164 mReachedTerminalState = true;
165 CompleteDecode();
168 return LexerResult(TerminalState::FAILURE);
171 bool Decoder::ShouldSyncDecode(size_t aByteLimit) {
172 MOZ_ASSERT(aByteLimit > 0);
173 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
175 return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
178 void Decoder::CompleteDecode() {
179 // Implementation-specific finalization.
180 nsresult rv = BeforeFinishInternal();
181 if (NS_FAILED(rv)) {
182 PostError();
185 rv = HasError() ? FinishWithErrorInternal() : FinishInternal();
186 if (NS_FAILED(rv)) {
187 PostError();
190 if (IsMetadataDecode()) {
191 // If this was a metadata decode and we never got a size, the decode failed.
192 if (!HasSize()) {
193 PostError();
195 return;
198 // If the implementation left us mid-frame, finish that up. Note that it may
199 // have left us transparent.
200 if (mInFrame) {
201 PostHasTransparency();
202 PostFrameStop();
205 // If PostDecodeDone() has not been called, we may need to send teardown
206 // notifications if it is unrecoverable.
207 if (!mDecodeDone) {
208 // We should always report an error to the console in this case.
209 mShouldReportError = true;
211 if (GetCompleteFrameCount() > 0) {
212 // We're usable if we have at least one complete frame, so do exactly
213 // what we should have when the decoder completed.
214 PostHasTransparency();
215 PostDecodeDone();
216 } else {
217 // We're not usable. Record some final progress indicating the error.
218 mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
222 if (mDecodeDone) {
223 MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
225 // If this image wasn't animated and isn't a transient image, mark its frame
226 // as optimizable. We don't support optimizing animated images and
227 // optimizing transient images isn't worth it.
228 if (!HasAnimation() &&
229 !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) && mCurrentFrame) {
230 mCurrentFrame->SetOptimizable();
235 void Decoder::SetOutputSize(const gfx::IntSize& aSize) {
236 mOutputSize = Some(aSize);
237 mHaveExplicitOutputSize = true;
240 Maybe<gfx::IntSize> Decoder::ExplicitOutputSize() const {
241 MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
242 return mHaveExplicitOutputSize ? mOutputSize : Nothing();
245 Maybe<uint32_t> Decoder::TakeCompleteFrameCount() {
246 const bool finishedNewFrame = mFinishedNewFrame;
247 mFinishedNewFrame = false;
248 return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
251 DecoderFinalStatus Decoder::FinalStatus() const {
252 return DecoderFinalStatus(IsMetadataDecode(), GetDecodeDone(), HasError(),
253 ShouldReportError());
256 DecoderTelemetry Decoder::Telemetry() const {
257 MOZ_ASSERT(mIterator);
258 return DecoderTelemetry(SpeedHistogram(),
259 mIterator ? mIterator->ByteCount() : 0,
260 mIterator ? mIterator->ChunkCount() : 0, mDecodeTime);
263 nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
264 gfx::SurfaceFormat aFormat,
265 const Maybe<AnimationParams>& aAnimParams) {
266 mCurrentFrame = AllocateFrameInternal(aOutputSize, aFormat, aAnimParams,
267 std::move(mCurrentFrame));
269 if (mCurrentFrame) {
270 mHasFrameToTake = true;
272 // Gather the raw pointers the decoders will use.
273 mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
275 // We should now be on |aFrameNum|. (Note that we're comparing the frame
276 // number, which is zero-based, with the frame count, which is one-based.)
277 MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount);
279 // If we're past the first frame, PostIsAnimated() should've been called.
280 MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
282 // Update our state to reflect the new frame.
283 MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
284 mInFrame = true;
287 return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
290 RawAccessFrameRef Decoder::AllocateFrameInternal(
291 const gfx::IntSize& aOutputSize, SurfaceFormat aFormat,
292 const Maybe<AnimationParams>& aAnimParams,
293 RawAccessFrameRef&& aPreviousFrame) {
294 if (HasError()) {
295 return RawAccessFrameRef();
298 uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0;
299 if (frameNum != mFrameCount) {
300 MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
301 return RawAccessFrameRef();
304 if (aOutputSize.width <= 0 || aOutputSize.height <= 0) {
305 NS_WARNING("Trying to add frame with zero or negative size");
306 return RawAccessFrameRef();
309 if (frameNum == 1) {
310 MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
311 aPreviousFrame->SetRawAccessOnly();
314 if (frameNum > 0) {
315 if (aPreviousFrame->GetDisposalMethod() !=
316 DisposalMethod::RESTORE_PREVIOUS) {
317 // If the new restore frame is the direct previous frame, then we know
318 // the dirty rect is composed only of the current frame's blend rect and
319 // the restore frame's clear rect (if applicable) which are handled in
320 // filters.
321 mRestoreFrame = std::move(aPreviousFrame);
322 mRestoreDirtyRect.SetBox(0, 0, 0, 0);
323 } else {
324 // We only need the previous frame's dirty rect, because while there may
325 // have been several frames between us and mRestoreFrame, the only areas
326 // that changed are the restore frame's clear rect, the current frame
327 // blending rect, and the previous frame's blending rect. All else is
328 // forgotten due to us restoring the same frame again.
329 mRestoreDirtyRect = aPreviousFrame->GetBoundedBlendRect();
333 RawAccessFrameRef ref;
335 // If we have a frame recycler, it must be for an animated image producing
336 // full frames. If the higher layers are discarding frames because of the
337 // memory footprint, then the recycler will allow us to reuse the buffers.
338 // Each frame should be the same size and have mostly the same properties.
339 if (mFrameRecycler) {
340 MOZ_ASSERT(aAnimParams);
342 ref = mFrameRecycler->RecycleFrame(mRecycleRect);
343 if (ref) {
344 // If the recycled frame is actually the current restore frame, we cannot
345 // use it. If the next restore frame is the new frame we are creating, in
346 // theory we could reuse it, but we would need to store the restore frame
347 // animation parameters elsewhere. For now we just drop it.
348 bool blocked = ref.get() == mRestoreFrame.get();
349 if (!blocked) {
350 blocked = NS_FAILED(ref->InitForDecoderRecycle(aAnimParams.ref()));
353 if (blocked) {
354 ref.reset();
359 // Either the recycler had nothing to give us, or we don't have a recycler.
360 // Produce a new frame to store the data.
361 if (!ref) {
362 // There is no underlying data to reuse, so reset the recycle rect to be
363 // the full frame, to ensure the restore frame is fully copied.
364 mRecycleRect = IntRect(IntPoint(0, 0), aOutputSize);
366 bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
367 auto frame = MakeNotNull<RefPtr<imgFrame>>();
368 if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult,
369 aAnimParams, bool(mFrameRecycler)))) {
370 NS_WARNING("imgFrame::Init should succeed");
371 return RawAccessFrameRef();
374 ref = frame->RawAccessRef();
375 if (!ref) {
376 frame->Abort();
377 return RawAccessFrameRef();
380 if (frameNum > 0) {
381 frame->SetRawAccessOnly();
385 mFrameCount++;
387 return ref;
391 * Hook stubs. Override these as necessary in decoder implementations.
394 nsresult Decoder::InitInternal() { return NS_OK; }
395 nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
396 nsresult Decoder::FinishInternal() { return NS_OK; }
398 nsresult Decoder::FinishWithErrorInternal() {
399 MOZ_ASSERT(!mInFrame);
400 return NS_OK;
404 * Progress Notifications
407 void Decoder::PostSize(int32_t aWidth, int32_t aHeight,
408 Orientation aOrientation /* = Orientation()*/) {
409 // Validate.
410 MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
411 MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");
413 // Set our intrinsic size.
414 mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
416 // Verify it is the expected size, if given. Note that this is only used by
417 // the ICO decoder for embedded image types, so only its subdecoders are
418 // required to handle failures in PostSize.
419 if (!IsExpectedSize()) {
420 PostError();
421 return;
424 // Set our output size if it's not already set.
425 if (!mOutputSize) {
426 mOutputSize = Some(IntSize(aWidth, aHeight));
429 MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight,
430 "Output size will result in upscaling");
432 // Create a downscaler if we need to downscale. This is used by legacy
433 // decoders that haven't been converted to use SurfacePipe yet.
434 // XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
435 if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) {
436 mDownscaler.emplace(*mOutputSize);
439 // Record this notification.
440 mProgress |= FLAG_SIZE_AVAILABLE;
443 void Decoder::PostHasTransparency() { mProgress |= FLAG_HAS_TRANSPARENCY; }
445 void Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout) {
446 mProgress |= FLAG_IS_ANIMATED;
447 mImageMetadata.SetHasAnimation();
448 mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
451 void Decoder::PostFrameStop(Opacity aFrameOpacity) {
452 // We should be mid-frame
453 MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
454 MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
455 MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
457 // Update our state.
458 mInFrame = false;
459 mFinishedNewFrame = true;
461 mCurrentFrame->Finish(aFrameOpacity, mFinalizeFrames);
463 mProgress |= FLAG_FRAME_COMPLETE;
465 mLoopLength += mCurrentFrame->GetTimeout();
467 if (mFrameCount == 1) {
468 // If we're not sending partial invalidations, then we send an invalidation
469 // here when the first frame is complete.
470 if (!ShouldSendPartialInvalidations()) {
471 mInvalidRect.UnionRect(mInvalidRect, IntRect(IntPoint(), Size()));
474 // If we dispose of the first frame by clearing it, then the first frame's
475 // refresh area is all of itself. RESTORE_PREVIOUS is invalid (assumed to
476 // be DISPOSE_CLEAR).
477 switch (mCurrentFrame->GetDisposalMethod()) {
478 default:
479 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
480 case DisposalMethod::CLEAR:
481 case DisposalMethod::CLEAR_ALL:
482 case DisposalMethod::RESTORE_PREVIOUS:
483 mFirstFrameRefreshArea = IntRect(IntPoint(), Size());
484 break;
485 case DisposalMethod::KEEP:
486 case DisposalMethod::NOT_SPECIFIED:
487 break;
489 } else {
490 // Some GIFs are huge but only have a small area that they animate. We only
491 // need to refresh that small area when frame 0 comes around again.
492 mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea,
493 mCurrentFrame->GetBoundedBlendRect());
497 void Decoder::PostInvalidation(const gfx::IntRect& aRect,
498 const Maybe<gfx::IntRect>& aRectAtOutputSize
499 /* = Nothing() */) {
500 // We should be mid-frame
501 MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
502 MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
504 // Record this invalidation, unless we're not sending partial invalidations
505 // or we're past the first frame.
506 if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
507 mInvalidRect.UnionRect(mInvalidRect, aRect);
508 mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect));
512 void Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */) {
513 MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
514 MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
515 MOZ_ASSERT(!mDecodeDone, "Decode already done!");
516 mDecodeDone = true;
518 mImageMetadata.SetLoopCount(aLoopCount);
520 // Some metadata that we track should take into account every frame in the
521 // image. If this is a first-frame-only decode, our accumulated loop length
522 // and first frame refresh area only includes the first frame, so it's not
523 // correct and we don't record it.
524 if (!IsFirstFrameDecode()) {
525 mImageMetadata.SetLoopLength(mLoopLength);
526 mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
529 mProgress |= FLAG_DECODE_COMPLETE;
532 void Decoder::PostError() {
533 mError = true;
535 if (mInFrame) {
536 MOZ_ASSERT(mCurrentFrame);
537 MOZ_ASSERT(mFrameCount > 0);
538 mCurrentFrame->Abort();
539 mInFrame = false;
540 --mFrameCount;
541 mHasFrameToTake = false;
545 } // namespace image
546 } // namespace mozilla