зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1293472
(Part 2) - Add AnimationSurfaceProvider. r=dholbert,edwin
This commit is contained in:
Родитель
aef6675e11
Коммит
367e148297
|
@ -0,0 +1,274 @@
|
|||
/* -*- 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 "gfxPrefs.h"
|
||||
#include "nsProxyRelease.h"
|
||||
|
||||
#include "Decoder.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
: ISurfaceProvider(AvailabilityState::StartAsPlaceholder())
|
||||
, mImage(aImage.get())
|
||||
, mDecodingMutex("AnimationSurfaceProvider::mDecoder")
|
||||
, mDecoder(aDecoder.get())
|
||||
, mFramesMutex("AnimationSurfaceProvider::mFrames")
|
||||
, mSurfaceKey(aSurfaceKey)
|
||||
{
|
||||
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
|
||||
"Use MetadataDecodingTask for metadata decodes");
|
||||
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
|
||||
"Use DecodedSurfaceProvider for single-frame image decodes");
|
||||
}
|
||||
|
||||
AnimationSurfaceProvider::~AnimationSurfaceProvider()
|
||||
{
|
||||
DropImageReference();
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::DropImageReference()
|
||||
{
|
||||
if (!mImage) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
// RasterImage objects need to be destroyed on the main thread. We also need
|
||||
// to destroy them asynchronously, because if our surface cache entry is
|
||||
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
|
||||
// destructor may call into the surface cache while whatever code caused us to
|
||||
// get evicted is holding the surface cache lock, causing deadlock.
|
||||
RefPtr<RasterImage> image = mImage;
|
||||
mImage = nullptr;
|
||||
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
|
||||
}
|
||||
|
||||
DrawableFrameRef
|
||||
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// If we don't have that frame, return an empty frame ref.
|
||||
if (aFrame >= mFrames.Length()) {
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
// We've got the requested frame. Return it.
|
||||
MOZ_ASSERT(mFrames[aFrame]);
|
||||
return mFrames[aFrame]->DrawableRef();
|
||||
}
|
||||
|
||||
bool
|
||||
AnimationSurfaceProvider::IsFinished() const
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
if (Availability().IsPlaceholder()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFrames.IsEmpty()) {
|
||||
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
|
||||
return false;
|
||||
}
|
||||
|
||||
// As long as we have at least one finished frame, we're finished.
|
||||
return mFrames[0]->IsFinished();
|
||||
}
|
||||
|
||||
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 = mSurfaceKey.Size();
|
||||
return 3 * size.width * size.height * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::Run()
|
||||
{
|
||||
MutexAutoLock lock(mDecodingMutex);
|
||||
|
||||
if (!mDecoder || !mImage) {
|
||||
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.
|
||||
CheckForNewFrameAtTerminalState();
|
||||
|
||||
// We're done!
|
||||
FinishDecoding();
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify for the progress we've made so far.
|
||||
if (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.
|
||||
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
|
||||
CheckForNewFrameAtYield();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtYield()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
// Try to get the new frame from the decoder.
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
|
||||
return;
|
||||
}
|
||||
|
||||
// We should've gotten a different frame than last time.
|
||||
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
|
||||
mFrames.LastElement().get() != frame.get());
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
bool justGotFirstFrame = false;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mFramesMutex);
|
||||
|
||||
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
|
||||
return; // We already have this one.
|
||||
}
|
||||
|
||||
// Append the new frame to the list.
|
||||
mFrames.AppendElement(Move(frame));
|
||||
|
||||
if (mFrames.Length() == 1) {
|
||||
justGotFirstFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (justGotFirstFrame) {
|
||||
AnnounceSurfaceAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::AnnounceSurfaceAvailable()
|
||||
{
|
||||
mFramesMutex.AssertNotCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
|
||||
// We just got the first frame; let the surface cache know.
|
||||
SurfaceCache::SurfaceAvailable(WrapNotNull(this),
|
||||
ImageKey(mImage.get()),
|
||||
mSurfaceKey);
|
||||
}
|
||||
|
||||
void
|
||||
AnimationSurfaceProvider::FinishDecoding()
|
||||
{
|
||||
mDecodingMutex.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mImage);
|
||||
MOZ_ASSERT(mDecoder);
|
||||
|
||||
// Send notifications.
|
||||
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
|
||||
|
||||
// Destroy our decoder; we don't need it anymore.
|
||||
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(gfxPrefs::ImageMemDecodeBytesAtATime());
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,104 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider for animated images.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_AnimationSurfaceProvider_h
|
||||
#define mozilla_image_AnimationSurfaceProvider_h
|
||||
|
||||
#include "FrameAnimator.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "ISurfaceProvider.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/**
|
||||
* An ISurfaceProvider that manages the decoding of animated images and
|
||||
* dynamically generates surfaces for the current playback state of the
|
||||
* animation.
|
||||
*/
|
||||
class AnimationSurfaceProvider final
|
||||
: public ISurfaceProvider
|
||||
, public IDecodingTask
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
|
||||
|
||||
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
|
||||
NotNull<Decoder*> aDecoder,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// ISurfaceProvider implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
// We use the ISurfaceProvider constructor of DrawableSurface to indicate that
|
||||
// our surfaces are computed lazily.
|
||||
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
|
||||
|
||||
bool IsFinished() const override;
|
||||
size_t LogicalSizeInBytes() const override;
|
||||
|
||||
protected:
|
||||
DrawableFrameRef DrawableRef(size_t aFrame) override;
|
||||
|
||||
// Animation frames are always locked. This is because we only want to release
|
||||
// their memory atomically (due to the surface cache discarding them). If they
|
||||
// were unlocked, the OS could end up releasing the memory of random frames
|
||||
// from the middle of the animation, which is not worth the complexity of
|
||||
// dealing with.
|
||||
bool IsLocked() const override { return true; }
|
||||
void SetLocked(bool) override { }
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// IDecodingTask implementation.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
void Run() override;
|
||||
bool ShouldPreferSyncRun() const override;
|
||||
|
||||
// Full decodes are low priority compared to metadata decodes because they
|
||||
// don't block layout or page load.
|
||||
TaskPriority Priority() const override { return TaskPriority::eLow; }
|
||||
|
||||
private:
|
||||
virtual ~AnimationSurfaceProvider();
|
||||
|
||||
void DropImageReference();
|
||||
void CheckForNewFrameAtYield();
|
||||
void CheckForNewFrameAtTerminalState();
|
||||
void AnnounceSurfaceAvailable();
|
||||
void FinishDecoding();
|
||||
|
||||
/// The image associated with our decoder.
|
||||
RefPtr<RasterImage> mImage;
|
||||
|
||||
/// A mutex to protect mDecoder. Always taken before mFramesMutex.
|
||||
mutable Mutex mDecodingMutex;
|
||||
|
||||
/// The decoder used to decode this animation.
|
||||
RefPtr<Decoder> mDecoder;
|
||||
|
||||
/// A mutex to protect mFrames. Always taken after mDecodingMutex.
|
||||
mutable Mutex mFramesMutex;
|
||||
|
||||
/// The frames of this animation, in order.
|
||||
nsTArray<RawAccessFrameRef> mFrames;
|
||||
|
||||
/// The key under which we're stored as a cache entry in the surface cache.
|
||||
const SurfaceKey mSurfaceKey;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_AnimationSurfaceProvider_h
|
|
@ -50,6 +50,7 @@ EXPORTS += [
|
|||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AnimationSurfaceProvider.cpp',
|
||||
'ClippedImage.cpp',
|
||||
'DecodedSurfaceProvider.cpp',
|
||||
'DecodePool.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче