summaryrefslogtreecommitdiffstats
path: root/image/AnimationSurfaceProvider.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /image/AnimationSurfaceProvider.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/AnimationSurfaceProvider.cpp')
-rw-r--r--image/AnimationSurfaceProvider.cpp534
1 files changed, 534 insertions, 0 deletions
diff --git a/image/AnimationSurfaceProvider.cpp b/image/AnimationSurfaceProvider.cpp
new file mode 100644
index 0000000000..05208a4d19
--- /dev/null
+++ b/image/AnimationSurfaceProvider.cpp
@@ -0,0 +1,534 @@
+/* -*- 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 "AnimationSurfaceProvider.h"
+
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "nsProxyRelease.h"
+
+#include "DecodePool.h"
+#include "Decoder.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+namespace image {
+
+AnimationSurfaceProvider::AnimationSurfaceProvider(
+ NotNull<RasterImage*> aImage, const SurfaceKey& aSurfaceKey,
+ NotNull<Decoder*> aDecoder, size_t aCurrentFrame)
+ : ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
+ AvailabilityState::StartAsPlaceholder()),
+ mImage(aImage.get()),
+ mDecodingMutex("AnimationSurfaceProvider::mDecoder"),
+ mDecoder(aDecoder.get()),
+ mFramesMutex("AnimationSurfaceProvider::mFrames"),
+ mCompositedFrameRequested(false),
+ mSharedAnimation(MakeRefPtr<SharedSurfacesAnimation>()) {
+ MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
+ "Use MetadataDecodingTask for metadata decodes");
+ MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
+ "Use DecodedSurfaceProvider for single-frame image decodes");
+
+ // Calculate how many frames we need to decode in this animation before we
+ // enter decode-on-demand mode.
+ IntSize frameSize = aSurfaceKey.Size();
+ size_t threshold =
+ (size_t(StaticPrefs::image_animated_decode_on_demand_threshold_kb()) *
+ 1024) /
+ (sizeof(uint32_t) * frameSize.width * frameSize.height);
+ size_t batch = StaticPrefs::image_animated_decode_on_demand_batch_size();
+
+ mFrames.reset(
+ new AnimationFrameRetainedBuffer(threshold, batch, aCurrentFrame));
+}
+
+AnimationSurfaceProvider::~AnimationSurfaceProvider() {
+ DropImageReference();
+
+ mSharedAnimation->Destroy();
+ if (mDecoder) {
+ mDecoder->SetFrameRecycler(nullptr);
+ }
+}
+
+void AnimationSurfaceProvider::DropImageReference() {
+ if (!mImage) {
+ return; // Nothing to do.
+ }
+
+ // RasterImage objects need to be destroyed on the main thread.
+ SurfaceCache::ReleaseImageOnMainThread(mImage.forget());
+}
+
+void AnimationSurfaceProvider::Reset() {
+ // We want to go back to the beginning.
+ bool mayDiscard;
+ bool restartDecoder = false;
+
+ {
+ MutexAutoLock lock(mFramesMutex);
+
+ // If we have not crossed the threshold, we know we haven't discarded any
+ // frames, and thus we know it is safe move our display index back to the
+ // very beginning. It would be cleaner to let the frame buffer make this
+ // decision inside the AnimationFrameBuffer::Reset method, but if we have
+ // crossed the threshold, we need to hold onto the decoding mutex too. We
+ // should avoid blocking the main thread on the decoder threads.
+ mayDiscard = mFrames->MayDiscard();
+ if (!mayDiscard) {
+ restartDecoder = mFrames->Reset();
+ }
+ }
+
+ if (mayDiscard) {
+ // We are over the threshold and have started discarding old frames. In
+ // that case we need to seize the decoding mutex. Thankfully we know that
+ // we are in the process of decoding at most the batch size frames, so
+ // this should not take too long to acquire.
+ MutexAutoLock lock(mDecodingMutex);
+
+ // We may have hit an error while redecoding. Because FrameAnimator is
+ // tightly coupled to our own state, that means we would need to go through
+ // some heroics to resume animating in those cases. The typical reason for
+ // a redecode to fail is out of memory, and recycling should prevent most of
+ // those errors. When image.animated.generate-full-frames has shipped
+ // enabled on a release or two, we can simply remove the old FrameAnimator
+ // blending code and simplify this quite a bit -- just always pop the next
+ // full frame and timeout off the stack.
+ if (mDecoder) {
+ mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
+ MOZ_ASSERT(mDecoder);
+
+ MutexAutoLock lock2(mFramesMutex);
+ restartDecoder = mFrames->Reset();
+ } else {
+ MOZ_ASSERT(mFrames->HasRedecodeError());
+ }
+ }
+
+ if (restartDecoder) {
+ DecodePool::Singleton()->AsyncRun(this);
+ }
+}
+
+void AnimationSurfaceProvider::Advance(size_t aFrame) {
+ bool restartDecoder;
+
+ RefPtr<SourceSurface> surface;
+ IntRect dirtyRect;
+ {
+ // Typical advancement of a frame.
+ MutexAutoLock lock(mFramesMutex);
+ restartDecoder = mFrames->AdvanceTo(aFrame);
+
+ imgFrame* frame = mFrames->Get(aFrame, /* aForDisplay */ true);
+ MOZ_ASSERT(frame);
+ if (aFrame != 0) {
+ dirtyRect = frame->GetDirtyRect();
+ } else {
+ MOZ_ASSERT(mFrames->SizeKnown());
+ dirtyRect = mFrames->FirstFrameRefreshArea();
+ }
+ surface = frame->GetSourceSurface();
+ MOZ_ASSERT(surface);
+ }
+
+ if (restartDecoder) {
+ DecodePool::Singleton()->AsyncRun(this);
+ }
+
+ mCompositedFrameRequested = false;
+ auto* sharedSurface = static_cast<SourceSurfaceSharedData*>(surface.get());
+ mSharedAnimation->SetCurrentFrame(sharedSurface, dirtyRect);
+}
+
+DrawableFrameRef AnimationSurfaceProvider::DrawableRef(size_t aFrame) {
+ MutexAutoLock lock(mFramesMutex);
+
+ if (Availability().IsPlaceholder()) {
+ MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
+ return DrawableFrameRef();
+ }
+
+ imgFrame* frame = mFrames->Get(aFrame, /* aForDisplay */ true);
+ if (!frame) {
+ return DrawableFrameRef();
+ }
+
+ return frame->DrawableRef();
+}
+
+already_AddRefed<imgFrame> AnimationSurfaceProvider::GetFrame(size_t aFrame) {
+ MutexAutoLock lock(mFramesMutex);
+
+ if (Availability().IsPlaceholder()) {
+ MOZ_ASSERT_UNREACHABLE("Calling GetFrame() on a placeholder");
+ return nullptr;
+ }
+
+ RefPtr<imgFrame> frame = mFrames->Get(aFrame, /* aForDisplay */ false);
+ MOZ_ASSERT_IF(frame, frame->IsFinished());
+ return frame.forget();
+}
+
+bool AnimationSurfaceProvider::IsFinished() const {
+ MutexAutoLock lock(mFramesMutex);
+
+ if (Availability().IsPlaceholder()) {
+ MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
+ return false;
+ }
+
+ return mFrames->IsFirstFrameFinished();
+}
+
+bool AnimationSurfaceProvider::IsFullyDecoded() const {
+ MutexAutoLock lock(mFramesMutex);
+ return mFrames->SizeKnown() && !mFrames->MayDiscard();
+}
+
+size_t AnimationSurfaceProvider::LogicalSizeInBytes() const {
+ // When decoding animated images, we need at most three live surfaces: the
+ // composited surface, the previous composited surface for
+ // DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding
+ // into. The composited surfaces are always BGRA. Although the surface we're
+ // decoding into may be paletted, and may be smaller than the real size of the
+ // image, we assume the worst case here.
+ // XXX(seth): Note that this is actually not accurate yet; we're storing the
+ // full sequence of frames, not just the three live surfaces mentioned above.
+ // Unfortunately there's no way to know in advance how many frames an
+ // animation has, so we really can't do better here. This will become correct
+ // once bug 1289954 is complete.
+ IntSize size = GetSurfaceKey().Size();
+ return 3 * size.width * size.height * sizeof(uint32_t);
+}
+
+void AnimationSurfaceProvider::AddSizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
+ // Note that the surface cache lock is already held here, and then we acquire
+ // mFramesMutex. For this method, this ordering is unavoidable, which means
+ // that we must be careful to always use the same ordering elsewhere.
+ MutexAutoLock lock(mFramesMutex);
+ mFrames->AddSizeOfExcludingThis(aMallocSizeOf, aCallback);
+}
+
+void AnimationSurfaceProvider::Run() {
+ MutexAutoLock lock(mDecodingMutex);
+
+ if (!mDecoder) {
+ MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
+ return;
+ }
+
+ while (true) {
+ // Run the decoder.
+ LexerResult result = mDecoder->Decode(WrapNotNull(this));
+
+ if (result.is<TerminalState>()) {
+ // We may have a new frame now, but it's not guaranteed - a decoding
+ // failure or truncated data may mean that no new frame got produced.
+ // Since we're not sure, rather than call CheckForNewFrameAtYield() here
+ // we call CheckForNewFrameAtTerminalState(), which handles both of these
+ // possibilities.
+ bool continueDecoding = CheckForNewFrameAtTerminalState();
+ FinishDecoding();
+
+ // Even if it is the last frame, we may not have enough frames buffered
+ // ahead of the current. If we are shutting down, we want to ensure we
+ // release the thread as soon as possible. The animation may advance even
+ // during shutdown, which keeps us decoding, and thus blocking the decode
+ // pool during teardown.
+ if (!mDecoder || !continueDecoding ||
+ DecodePool::Singleton()->IsShuttingDown()) {
+ return;
+ }
+
+ // Restart from the very beginning because the decoder was recreated.
+ continue;
+ }
+
+ // If there is output available we want to change the entry in the surface
+ // cache from a placeholder to an actual surface now before NotifyProgress
+ // call below so that when consumers get the frame complete notification
+ // from the NotifyProgress they can actually get a surface from the surface
+ // cache.
+ bool checkForNewFrameAtYieldResult = false;
+ if (result == LexerResult(Yield::OUTPUT_AVAILABLE)) {
+ checkForNewFrameAtYieldResult = CheckForNewFrameAtYield();
+ }
+
+ // Notify for the progress we've made so far.
+ if (mImage && mDecoder->HasProgress()) {
+ NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
+ }
+
+ if (result == LexerResult(Yield::NEED_MORE_DATA)) {
+ // We can't make any more progress right now. The decoder itself will
+ // ensure that we get reenqueued when more data is available; just return
+ // for now.
+ return;
+ }
+
+ // There's new output available - a new frame! Grab it. If we don't need any
+ // more for the moment we can break out of the loop. If we are shutting
+ // down, we want to ensure we release the thread as soon as possible. The
+ // animation may advance even during shutdown, which keeps us decoding, and
+ // thus blocking the decode pool during teardown.
+ MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
+ if (!checkForNewFrameAtYieldResult ||
+ DecodePool::Singleton()->IsShuttingDown()) {
+ return;
+ }
+ }
+}
+
+bool AnimationSurfaceProvider::CheckForNewFrameAtYield() {
+ mDecodingMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ bool justGotFirstFrame = false;
+ bool continueDecoding = false;
+
+ {
+ MutexAutoLock lock(mFramesMutex);
+
+ // Try to get the new frame from the decoder.
+ RefPtr<imgFrame> frame = mDecoder->GetCurrentFrame();
+ MOZ_ASSERT(mDecoder->HasFrameToTake());
+ mDecoder->ClearHasFrameToTake();
+
+ if (!frame) {
+ MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
+ return true;
+ }
+
+ // We should've gotten a different frame than last time.
+ MOZ_ASSERT(!mFrames->IsLastInsertedFrame(frame));
+
+ // Append the new frame to the list.
+ AnimationFrameBuffer::InsertStatus status =
+ mFrames->Insert(std::move(frame));
+
+ // If we hit a redecode error, then we actually want to stop. This happens
+ // when we tried to insert more frames than we originally had (e.g. the
+ // original decoder attempt hit an OOM error sooner than we did). Better to
+ // stop the animation than to get out of sync with FrameAnimator.
+ if (mFrames->HasRedecodeError()) {
+ mDecoder = nullptr;
+ return false;
+ }
+
+ switch (status) {
+ case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE:
+ continueDecoding = true;
+ [[fallthrough]];
+ case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
+ RequestFrameDiscarding();
+ break;
+ case AnimationFrameBuffer::InsertStatus::CONTINUE:
+ continueDecoding = true;
+ break;
+ case AnimationFrameBuffer::InsertStatus::YIELD:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled insert status!");
+ break;
+ }
+
+ // We only want to handle the first frame if it is the first pass for the
+ // animation decoder. The owning image will be cleared after that.
+ size_t frameCount = mFrames->Size();
+ if (frameCount == 1 && mImage) {
+ justGotFirstFrame = true;
+ }
+ }
+
+ if (justGotFirstFrame) {
+ AnnounceSurfaceAvailable();
+ }
+
+ return continueDecoding;
+}
+
+bool AnimationSurfaceProvider::CheckForNewFrameAtTerminalState() {
+ mDecodingMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ bool justGotFirstFrame = false;
+ bool continueDecoding;
+
+ {
+ MutexAutoLock lock(mFramesMutex);
+
+ // The decoder may or may not have a new frame for us at this point. Avoid
+ // reinserting the same frame again.
+ RefPtr<imgFrame> frame = mDecoder->GetCurrentFrame();
+
+ // If the decoder didn't finish a new frame (ie if, after starting the
+ // frame, it got an error and aborted the frame and the rest of the decode)
+ // that means it won't be reporting it to the image or FrameAnimator so we
+ // should ignore it too, that's what HasFrameToTake tracks basically.
+ if (!mDecoder->HasFrameToTake()) {
+ frame = nullptr;
+ } else {
+ MOZ_ASSERT(frame);
+ mDecoder->ClearHasFrameToTake();
+ }
+
+ if (!frame || mFrames->IsLastInsertedFrame(frame)) {
+ return mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea());
+ }
+
+ // Append the new frame to the list.
+ AnimationFrameBuffer::InsertStatus status =
+ mFrames->Insert(std::move(frame));
+
+ // If we hit a redecode error, then we actually want to stop. This will be
+ // fully handled in FinishDecoding.
+ if (mFrames->HasRedecodeError()) {
+ return false;
+ }
+
+ switch (status) {
+ case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE:
+ case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
+ RequestFrameDiscarding();
+ break;
+ case AnimationFrameBuffer::InsertStatus::CONTINUE:
+ case AnimationFrameBuffer::InsertStatus::YIELD:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled insert status!");
+ break;
+ }
+
+ continueDecoding =
+ mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea());
+
+ // We only want to handle the first frame if it is the first pass for the
+ // animation decoder. The owning image will be cleared after that.
+ if (mFrames->Size() == 1 && mImage) {
+ justGotFirstFrame = true;
+ }
+ }
+
+ if (justGotFirstFrame) {
+ AnnounceSurfaceAvailable();
+ }
+
+ return continueDecoding;
+}
+
+void AnimationSurfaceProvider::RequestFrameDiscarding() {
+ mDecodingMutex.AssertCurrentThreadOwns();
+ mFramesMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ if (mFrames->MayDiscard() || mFrames->IsRecycling()) {
+ MOZ_ASSERT_UNREACHABLE("Already replaced frame queue!");
+ return;
+ }
+
+ auto oldFrameQueue =
+ static_cast<AnimationFrameRetainedBuffer*>(mFrames.get());
+
+ MOZ_ASSERT(!mDecoder->GetFrameRecycler());
+ if (StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()) {
+ mFrames.reset(new AnimationFrameRecyclingQueue(std::move(*oldFrameQueue)));
+ mDecoder->SetFrameRecycler(this);
+ } else {
+ mFrames.reset(new AnimationFrameDiscardingQueue(std::move(*oldFrameQueue)));
+ }
+}
+
+void AnimationSurfaceProvider::AnnounceSurfaceAvailable() {
+ mFramesMutex.AssertNotCurrentThreadOwns();
+ MOZ_ASSERT(mImage);
+
+ // We just got the first frame; let the surface cache know. We deliberately do
+ // this outside of mFramesMutex to avoid a potential deadlock with
+ // AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex
+ // and then the surface cache lock, while the memory reporting code would
+ // acquire the surface cache lock and then mFramesMutex.
+ SurfaceCache::SurfaceAvailable(WrapNotNull(this));
+}
+
+void AnimationSurfaceProvider::FinishDecoding() {
+ mDecodingMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mDecoder);
+
+ if (mImage) {
+ // Send notifications.
+ NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
+ }
+
+ // Determine if we need to recreate the decoder, in case we are discarding
+ // frames and need to loop back to the beginning.
+ bool recreateDecoder;
+ {
+ MutexAutoLock lock(mFramesMutex);
+ recreateDecoder = !mFrames->HasRedecodeError() && mFrames->MayDiscard();
+ }
+
+ if (recreateDecoder) {
+ mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
+ MOZ_ASSERT(mDecoder);
+ } else {
+ mDecoder = nullptr;
+ }
+
+ // We don't need a reference to our image anymore, either, and we don't want
+ // one. We may be stored in the surface cache for a long time after decoding
+ // finishes. If we don't drop our reference to the image, we'll end up
+ // keeping it alive as long as we remain in the surface cache, which could
+ // greatly extend the image's lifetime - in fact, if the image isn't
+ // discardable, it'd result in a leak!
+ DropImageReference();
+}
+
+bool AnimationSurfaceProvider::ShouldPreferSyncRun() const {
+ MutexAutoLock lock(mDecodingMutex);
+ MOZ_ASSERT(mDecoder);
+
+ return mDecoder->ShouldSyncDecode(
+ StaticPrefs::image_mem_decode_bytes_at_a_time_AtStartup());
+}
+
+RawAccessFrameRef AnimationSurfaceProvider::RecycleFrame(
+ gfx::IntRect& aRecycleRect) {
+ MutexAutoLock lock(mFramesMutex);
+ MOZ_ASSERT(mFrames->IsRecycling());
+ return mFrames->RecycleFrame(aRecycleRect);
+}
+
+nsresult AnimationSurfaceProvider::UpdateKey(
+ layers::RenderRootStateManager* aManager,
+ wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<SourceSurface> surface;
+ {
+ MutexAutoLock lock(mFramesMutex);
+ imgFrame* frame =
+ mFrames->Get(mFrames->Displayed(), /* aForDisplay */ true);
+ if (!frame) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ surface = frame->GetSourceSurface();
+ }
+
+ mCompositedFrameRequested = true;
+ auto* sharedSurface = static_cast<SourceSurfaceSharedData*>(surface.get());
+ return mSharedAnimation->UpdateKey(sharedSurface, aManager, aResources, aKey);
+}
+
+} // namespace image
+} // namespace mozilla