Bug 1874523. r=tnikkel

Differential Revision: https://phabricator.services.mozilla.com/D227304
This commit is contained in:
Andrew Osmond
2024-11-25 17:55:49 +00:00
parent 3f9a172757
commit e1368b4e31
5 changed files with 90 additions and 80 deletions

View File

@@ -432,32 +432,38 @@ RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
RawAccessFrameRef recycledFrame;
if (mRecycle.front().mFrame) {
recycledFrame = mRecycle.front().mFrame->RawAccessRef();
MOZ_ASSERT(recycledFrame);
recycledFrame = mRecycle.front().mFrame->RawAccessRef(
gfx::DataSourceSurface::READ_WRITE);
mRecycle.pop_front();
if (mForceUseFirstFrameRefreshArea) {
// We are still crossing the loop boundary and cannot rely upon the dirty
// rects of entries in mDisplay to be representative. E.g. The first frame
// is probably has a full frame dirty rect.
aRecycleRect = mFirstFrameRefreshArea;
} else {
// Calculate the recycle rect for the recycled frame. This is the
// cumulative dirty rect of all of the frames ahead of us to be displayed,
// and to be used for recycling. Or in other words, the dirty rect between
// the recycled frame and the decoded frame which reuses the buffer.
//
// We know at this point that mRecycle contains either frames from the end
// of the animation with the first frame refresh area as the dirty rect
// (plus the first frame likewise) and frames with their actual dirty rect
// from the start. mDisplay should also only contain frames from the start
// of the animation onwards.
aRecycleRect.SetRect(0, 0, 0, 0);
for (const RefPtr<imgFrame>& frame : mDisplay) {
aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
}
for (const RecycleEntry& entry : mRecycle) {
aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
// If we couldn't map in the surface, it is probably because the frame was
// finalized and we did not expect to need to write into it again. This
// happens for the first frames produced during an animation.
if (recycledFrame) {
if (mForceUseFirstFrameRefreshArea) {
// We are still crossing the loop boundary and cannot rely upon the
// dirty rects of entries in mDisplay to be representative. E.g. The
// first frame is probably has a full frame dirty rect.
aRecycleRect = mFirstFrameRefreshArea;
} else {
// Calculate the recycle rect for the recycled frame. This is the
// cumulative dirty rect of all of the frames ahead of us to be
// displayed, and to be used for recycling. Or in other words, the dirty
// rect between the recycled frame and the decoded frame which reuses
// the buffer.
//
// We know at this point that mRecycle contains either frames from the
// end of the animation with the first frame refresh area as the dirty
// rect (plus the first frame likewise) and frames with their actual
// dirty rect from the start. mDisplay should also only contain frames
// from the start of the animation onwards.
aRecycleRect.SetRect(0, 0, 0, 0);
for (const RefPtr<imgFrame>& frame : mDisplay) {
aRecycleRect = aRecycleRect.Union(frame->GetDirtyRect());
}
for (const RecycleEntry& entry : mRecycle) {
aRecycleRect = aRecycleRect.Union(entry.mDirtyRect);
}
}
}
} else {

View File

@@ -316,8 +316,7 @@ nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
if (mCurrentFrame) {
mHasFrameToTake = true;
// Gather the raw pointers the decoders will use.
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
mImageData = mCurrentFrame.Data();
// 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.)
@@ -329,6 +328,9 @@ nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize,
// Update our state to reflect the new frame.
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
mInFrame = true;
} else {
mImageData = nullptr;
mImageDataLength = 0;
}
return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
@@ -389,7 +391,8 @@ RawAccessFrameRef Decoder::AllocateFrameInternal(
// animation parameters elsewhere. For now we just drop it.
bool blocked = ref.get() == mRestoreFrame.get();
if (!blocked) {
blocked = NS_FAILED(ref->InitForDecoderRecycle(aAnimParams.ref()));
blocked = NS_FAILED(
ref->InitForDecoderRecycle(aAnimParams.ref(), &mImageDataLength));
}
if (blocked) {
@@ -408,12 +411,13 @@ RawAccessFrameRef Decoder::AllocateFrameInternal(
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
auto frame = MakeNotNull<RefPtr<imgFrame>>();
if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult,
aAnimParams, bool(mFrameRecycler)))) {
aAnimParams, bool(mFrameRecycler),
&mImageDataLength))) {
NS_WARNING("imgFrame::Init should succeed");
return RawAccessFrameRef();
}
ref = frame->RawAccessRef();
ref = frame->RawAccessRef(gfx::DataSourceSurface::READ_WRITE);
if (!ref) {
frame->Abort();
return RawAccessFrameRef();

View File

@@ -145,7 +145,8 @@ imgFrame::~imgFrame() {
nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize,
SurfaceFormat aFormat, bool aNonPremult,
const Maybe<AnimationParams>& aAnimParams,
bool aShouldRecycle) {
bool aShouldRecycle,
uint32_t* aImageDataLength) {
// Assert for properties that should be verified by decoders,
// warn for properties related to bad content.
if (!SurfaceCache::IsLegalSize(aImageSize)) {
@@ -217,10 +218,15 @@ nsresult imgFrame::InitForDecoder(const nsIntSize& aImageSize,
}
}
if (aImageDataLength) {
*aImageDataLength = GetImageDataLength();
}
return NS_OK;
}
nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams,
uint32_t* aImageDataLength) {
// We want to recycle this frame, but there is no guarantee that consumers are
// done with it in a timely manner. Let's ensure they are done with it first.
MonitorAutoLock lock(mMonitor);
@@ -287,6 +293,10 @@ nsresult imgFrame::InitForDecoderRecycle(const AnimationParams& aAnimParams) {
mDisposalMethod = aAnimParams.mDisposalMethod;
mDirtyRect = GetRect();
if (aImageDataLength) {
*aImageDataLength = GetImageDataLength();
}
return NS_OK;
}
@@ -391,7 +401,10 @@ nsresult imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
DrawableFrameRef imgFrame::DrawableRef() { return DrawableFrameRef(this); }
RawAccessFrameRef imgFrame::RawAccessRef() { return RawAccessFrameRef(this); }
RawAccessFrameRef imgFrame::RawAccessRef(
gfx::DataSourceSurface::MapType aMapType) {
return RawAccessFrameRef(this, aMapType);
}
imgFrame::SurfaceWithFormat imgFrame::SurfaceForDrawing(
bool aDoPartialDecode, bool aDoTile, ImageRegion& aRegion,
@@ -586,36 +599,6 @@ uint32_t imgFrame::GetImageDataLength() const {
return GetImageBytesPerRow() * mImageSize.height;
}
void imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const {
MonitorAutoLock lock(mMonitor);
GetImageDataInternal(aData, aLength);
}
void imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const {
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(mRawSurface);
if (mRawSurface) {
// TODO: This is okay for now because we only realloc shared surfaces on
// the main thread after decoding has finished, but if animations want to
// read frame data off the main thread, we will need to reconsider this.
*aData = mRawSurface->GetData();
MOZ_ASSERT(*aData,
"mRawSurface is non-null, but GetData is null in GetImageData");
} else {
*aData = nullptr;
}
*aLength = GetImageDataLength();
}
uint8_t* imgFrame::GetImageData() const {
uint8_t* data;
uint32_t length;
GetImageData(&data, &length);
return data;
}
void imgFrame::FinalizeSurface() {
MonitorAutoLock lock(mMonitor);
FinalizeSurfaceInternal();

View File

@@ -55,7 +55,8 @@ class imgFrame {
nsresult InitForDecoder(const nsIntSize& aImageSize, SurfaceFormat aFormat,
bool aNonPremult,
const Maybe<AnimationParams>& aAnimParams,
bool aShouldRecycle);
bool aShouldRecycle,
uint32_t* aImageDataLength = nullptr);
/**
* Reinitialize this imgFrame with the new parameters, but otherwise retain
@@ -65,7 +66,8 @@ class imgFrame {
* given an IDecoderFrameRecycler object which may yield a recycled imgFrame
* that was discarded to save memory.
*/
nsresult InitForDecoderRecycle(const AnimationParams& aAnimParams);
nsresult InitForDecoderRecycle(const AnimationParams& aAnimParams,
uint32_t* aImageDataLength = nullptr);
/**
* Initialize this imgFrame with a new surface and draw the provided
@@ -90,7 +92,8 @@ class imgFrame {
/**
* Create a RawAccessFrameRef for the frame.
*/
RawAccessFrameRef RawAccessRef();
RawAccessFrameRef RawAccessRef(
gfx::DataSourceSurface::MapType aMapType = gfx::DataSourceSurface::READ);
bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
SamplingFilter aSamplingFilter, uint32_t aImageFlags,
@@ -160,8 +163,6 @@ class imgFrame {
BlendMethod GetBlendMethod() const { return mBlendMethod; }
DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
bool FormatHasAlpha() const { return mFormat == SurfaceFormat::OS_RGBA; }
void GetImageData(uint8_t** aData, uint32_t* length) const;
uint8_t* GetImageData() const;
const IntRect& GetDirtyRect() const { return mDirtyRect; }
void SetDirtyRect(const IntRect& aDirtyRect) { mDirtyRect = aDirtyRect; }
@@ -186,7 +187,6 @@ class imgFrame {
bool AreAllPixelsWritten() const MOZ_REQUIRES(mMonitor);
nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
void GetImageDataInternal(uint8_t** aData, uint32_t* length) const;
uint32_t GetImageBytesPerRow() const;
uint32_t GetImageDataLength() const;
void FinalizeSurfaceInternal();
@@ -356,13 +356,25 @@ class DrawableFrameRef final {
*/
class RawAccessFrameRef final {
public:
RawAccessFrameRef() : mData(nullptr) {}
RawAccessFrameRef() = default;
explicit RawAccessFrameRef(imgFrame* aFrame)
: mFrame(aFrame), mData(nullptr) {
explicit RawAccessFrameRef(imgFrame* aFrame,
gfx::DataSourceSurface::MapType aMapType)
: mFrame(aFrame) {
MOZ_ASSERT(mFrame, "Need a frame");
mData = mFrame->GetImageData();
// Note that we do not use ScopedMap here because it holds a strong
// reference to the underlying surface. This affects the reuse logic for
// recycling in imgFrame::InitForDecoderRecycle.
{
MonitorAutoLock lock(mFrame->mMonitor);
gfx::DataSourceSurface::MappedSurface map;
if (mFrame->mRawSurface && mFrame->mRawSurface->Map(aMapType, &map)) {
MOZ_ASSERT(map.mData);
mData = map.mData;
}
}
if (!mData) {
mFrame = nullptr;
}
@@ -373,11 +385,15 @@ class RawAccessFrameRef final {
aOther.mData = nullptr;
}
~RawAccessFrameRef() = default;
~RawAccessFrameRef() { reset(); }
RawAccessFrameRef& operator=(RawAccessFrameRef&& aOther) {
MOZ_ASSERT(this != &aOther, "Self-moves are prohibited");
if (mFrame) {
MonitorAutoLock lock(mFrame->mMonitor);
mFrame->mRawSurface->Unmap();
}
mFrame = std::move(aOther.mFrame);
mData = aOther.mData;
aOther.mData = nullptr;
@@ -401,6 +417,10 @@ class RawAccessFrameRef final {
const imgFrame* get() const { return mFrame; }
void reset() {
if (mFrame) {
MonitorAutoLock lock(mFrame->mMonitor);
mFrame->mRawSurface->Unmap();
}
mFrame = nullptr;
mData = nullptr;
}
@@ -412,7 +432,7 @@ class RawAccessFrameRef final {
RawAccessFrameRef& operator=(const RawAccessFrameRef& aOther) = delete;
RefPtr<imgFrame> mFrame;
uint8_t* mData;
uint8_t* mData = nullptr;
};
} // namespace image

View File

@@ -759,8 +759,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingResetBeforeComplete) {
while (!buffer.Recycle().empty()) {
gfx::IntRect recycleRect;
RawAccessFrameRef frameRef = buffer.RecycleFrame(recycleRect);
EXPECT_TRUE(frameRef);
EXPECT_FALSE(ReinitForRecycle(frameRef));
EXPECT_FALSE(frameRef);
}
// Reinsert the first two frames as recyclable and reset again.
@@ -829,8 +828,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingRect) {
gfx::IntRect recycleRect;
EXPECT_FALSE(buffer.Recycle().empty());
RawAccessFrameRef frameRef = buffer.RecycleFrame(recycleRect);
EXPECT_TRUE(frameRef);
EXPECT_FALSE(ReinitForRecycle(frameRef));
EXPECT_FALSE(frameRef);
EXPECT_TRUE(buffer.Recycle().empty());
// Insert a recyclable partial frame. Its dirty rect shouldn't matter since
@@ -842,8 +840,7 @@ TEST_F(ImageAnimationFrameBuffer, RecyclingRect) {
VerifyAdvance(buffer, 5, true);
EXPECT_FALSE(buffer.Recycle().empty());
frameRef = buffer.RecycleFrame(recycleRect);
EXPECT_TRUE(frameRef);
EXPECT_FALSE(ReinitForRecycle(frameRef));
EXPECT_FALSE(frameRef);
EXPECT_TRUE(buffer.Recycle().empty());
// Insert a recyclable partial frame. Its dirty rect should match the recycle