Issue #2073 - m-c 1382683: Accelerate GIF decoding to SurfacePipe

1. Implement SurfacePipe::WritePixelBlocks for faster writing of pixels
2. Switch nsGIFDecoder2 to write pixels in blocks instead of individually
This commit is contained in:
Martok 2022-12-30 21:37:59 +01:00 • committed by roytam1
commit 7d75c2717f
3 changed files with 205 additions and 88 deletions

View file

@ -28,6 +28,7 @@
#include "mozilla/Likely.h" #include "mozilla/Likely.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/Move.h" #include "mozilla/Move.h"
#include "mozilla/Tuple.h"
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
#include "mozilla/Variant.h" #include "mozilla/Variant.h"
@ -175,6 +176,43 @@ public:
return *result; return *result;
} }
/**
* Write pixels to the surface by calling a lambda which may write as many
* pixels as there is remaining to complete the row. It is not completely
* memory safe as it trusts the underlying decoder not to overrun the given
* buffer, however it is an acceptable tradeoff for performance.
*
* Writing continues until every pixel in the surface has been written to
* (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState
* which WritePixelBlocks() will return to the caller.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aFunc A lambda that functions as a generator, yielding at most the
* maximum number of pixels requested. The lambda must accept a
* pointer argument to the first pixel to write, a maximum
* number of pixels to write as part of the block, and return a
* NextPixel<PixelType> value.
*
* @return A WriteState value indicating the lambda generator's state.
* WritePixelBlocks() itself will return WriteState::FINISHED if
* writing has finished, regardless of the lambda's internal state.
*/
template <typename PixelType, typename Func>
WriteState WritePixelBlocks(Func aFunc)
{
Maybe<WriteState> result;
while (!(result = DoWritePixelBlockToRow<PixelType>(Forward<Func>(aFunc)))) { }
return *result;
}
/** /**
* A variant of WritePixels() that writes a single row of pixels to the * A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels. * surface one at a time by repeatedly calling a lambda that yields pixels.
@ -449,6 +487,50 @@ protected:
private: private:
/**
* An internal method used to implement WritePixelBlocks. This method writes
* up to the number of pixels necessary to complete the row and returns Some()
* if we either finished the entire surface or the lambda returned a
* WriteState indicating that we should return to the caller. If the row was
* successfully written without either of those things happening, it returns
* Nothing(), allowing WritePixelBlocks() to iterate to fill as many rows as
* possible.
*/
template <typename PixelType, typename Func>
Maybe<WriteState> DoWritePixelBlockToRow(Func aFunc)
{
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
if (IsSurfaceFinished()) {
return Some(WriteState::FINISHED); // We're already done.
}
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
int32_t remainder = mInputSize.width - mCol;
int32_t written;
Maybe<WriteState> result;
Tie(written, result) = aFunc(&rowPtr[mCol], remainder);
if (written == remainder) {
MOZ_ASSERT(result.isNothing());
mCol = mInputSize.width;
AdvanceRow(); // We've finished the row.
return IsSurfaceFinished() ? Some(WriteState::FINISHED)
: Nothing();
}
MOZ_ASSERT(written >= 0 && written < remainder);
MOZ_ASSERT(result.isSome());
mCol += written;
if (*result == WriteState::FINISHED) {
ZeroOutRestOfSurface<PixelType>();
}
return result;
}
/** /**
* An internal method used to implement both WritePixels() and * An internal method used to implement both WritePixels() and
* WritePixelsToRow(). Those methods differ only in their behavior after a row * WritePixelsToRow(). Those methods differ only in their behavior after a row
@ -607,6 +689,20 @@ public:
return mHead->WritePixels<PixelType>(Forward<Func>(aFunc)); return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
} }
/**
* A variant of WritePixels() that writes up to a single row of pixels to the
* surface in blocks by repeatedly calling a lambda that yields up to the
* requested number of pixels.
*
* @see SurfaceFilter::WritePixelBlocks() for the canonical documentation.
*/
template <typename PixelType, typename Func>
WriteState WritePixelBlocks(Func aFunc)
{
MOZ_ASSERT(mHead, "Use before configured!");
return mHead->WritePixelBlocks<PixelType>(Forward<Func>(aFunc));
}
/** /**
* A variant of WritePixels() that writes a single row of pixels to the * A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels. * surface one at a time by repeatedly calling a lambda that yields pixels.

View file

@ -299,10 +299,12 @@ nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
} }
template <typename PixelSize> template <typename PixelSize>
NextPixel<PixelSize> Tuple<int32_t, Maybe<WriteState>>
nsGIFDecoder2::YieldPixel(const uint8_t* aData, nsGIFDecoder2::YieldPixels(const uint8_t* aData,
size_t aLength, size_t aLength,
size_t* aBytesReadOut) size_t* aBytesReadOut,
PixelSize* aPixelBlock,
int32_t aBlockSize)
{ {
MOZ_ASSERT(aData); MOZ_ASSERT(aData);
MOZ_ASSERT(aBytesReadOut); MOZ_ASSERT(aBytesReadOut);
@ -311,6 +313,8 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
// Advance to the next byte we should read. // Advance to the next byte we should read.
const uint8_t* data = aData + *aBytesReadOut; const uint8_t* data = aData + *aBytesReadOut;
int32_t written = 0;
while (aBlockSize > written) {
// If we don't have any decoded data to yield, try to read some input and // If we don't have any decoded data to yield, try to read some input and
// produce some. // produce some.
if (mGIFStruct.stackp == mGIFStruct.stack) { if (mGIFStruct.stackp == mGIFStruct.stack) {
@ -323,7 +327,7 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
} }
if (mGIFStruct.bits < mGIFStruct.codesize) { if (mGIFStruct.bits < mGIFStruct.codesize) {
return AsVariant(WriteState::NEED_MORE_DATA); return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
} }
// Get the leading variable-length symbol from the data stream. // Get the leading variable-length symbol from the data stream.
@ -339,26 +343,29 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
mGIFStruct.avail = clearCode + 2; mGIFStruct.avail = clearCode + 2;
mGIFStruct.oldcode = -1; mGIFStruct.oldcode = -1;
return AsVariant(WriteState::NEED_MORE_DATA); return MakeTuple(written, Some(WriteState::NEED_MORE_DATA));
} }
// Check for explicit end-of-stream code. It should only appear after all // Check for explicit end-of-stream code. It should only appear after all
// image data, but if that was the case we wouldn't be in this function, so // image data, but if that was the case we wouldn't be in this function, so
// this is always an error condition. // this is always an error condition.
if (code == (clearCode + 1)) { if (code == (clearCode + 1)) {
return AsVariant(WriteState::FAILURE); return MakeTuple(written, Some(WriteState::FAILURE));
} }
if (mGIFStruct.oldcode == -1) { if (mGIFStruct.oldcode == -1) {
if (code >= MAX_BITS) { if (code >= MAX_BITS) {
return AsVariant(WriteState::FAILURE); // The code's too big; something's wrong. // The code's too big; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
} }
mGIFStruct.firstchar = mGIFStruct.oldcode = code; mGIFStruct.firstchar = mGIFStruct.oldcode = code;
// Yield a pixel at the appropriate index in the colormap. // Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--; mGIFStruct.pixels_remaining--;
return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code])); aPixelBlock[written++] =
ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]);
continue;
} }
int incode = code; int incode = code;
@ -367,20 +374,22 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
code = mGIFStruct.oldcode; code = mGIFStruct.oldcode;
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) { if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong. // Stack overflow; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
} }
} }
while (code >= clearCode) { while (code >= clearCode) {
if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) { if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
return AsVariant(WriteState::FAILURE); return MakeTuple(written, Some(WriteState::FAILURE));
} }
*mGIFStruct.stackp++ = mGIFStruct.suffix[code]; *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
code = mGIFStruct.prefix[code]; code = mGIFStruct.prefix[code];
if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) { if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
return AsVariant(WriteState::FAILURE); // Stack overflow; something's wrong. // Stack overflow; something's wrong.
return MakeTuple(written, Some(WriteState::FAILURE));
} }
} }
@ -407,12 +416,16 @@ nsGIFDecoder2::YieldPixel(const uint8_t* aData,
if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) { if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?"); MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
return AsVariant(WriteState::FAILURE); return MakeTuple(written, Some(WriteState::FAILURE));
} }
// Yield a pixel at the appropriate index in the colormap. // Yield a pixel at the appropriate index in the colormap.
mGIFStruct.pixels_remaining--; mGIFStruct.pixels_remaining--;
return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp)); aPixelBlock[written++]
= ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp);
}
return MakeTuple(written, Maybe<WriteState>());
} }
/// Expand the colormap from RGB to Packed ARGB as needed by Cairo. /// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
@ -1030,8 +1043,12 @@ nsGIFDecoder2::ReadLZWData(const char* aData, size_t aLength)
size_t bytesRead = 0; size_t bytesRead = 0;
auto result = mGIFStruct.images_decoded == 0 auto result = mGIFStruct.images_decoded == 0
? mPipe.WritePixels<uint32_t>([&]{ return YieldPixel<uint32_t>(data, length, &bytesRead); }) ? mPipe.WritePixelBlocks<uint32_t>([&](uint32_t* aPixelBlock, int32_t aBlockSize) {
: mPipe.WritePixels<uint8_t>([&]{ return YieldPixel<uint8_t>(data, length, &bytesRead); }); return YieldPixels<uint32_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
})
: mPipe.WritePixelBlocks<uint8_t>([&](uint8_t* aPixelBlock, int32_t aBlockSize) {
return YieldPixels<uint8_t>(data, length, &bytesRead, aPixelBlock, aBlockSize);
});
if (MOZ_UNLIKELY(bytesRead > length)) { if (MOZ_UNLIKELY(bytesRead > length)) {
MOZ_ASSERT_UNREACHABLE("Overread?"); MOZ_ASSERT_UNREACHABLE("Overread?");

View file

@ -61,8 +61,12 @@ private:
ColormapIndexToPixel(uint8_t aIndex); ColormapIndexToPixel(uint8_t aIndex);
/// A generator function that performs LZW decompression and yields pixels. /// A generator function that performs LZW decompression and yields pixels.
template <typename PixelSize> NextPixel<PixelSize> template <typename PixelSize> Tuple<int32_t, Maybe<WriteState>>
YieldPixel(const uint8_t* aData, size_t aLength, size_t* aBytesReadOut); YieldPixels(const uint8_t* aData,
size_t aLength,
size_t* aBytesReadOut,
PixelSize* aPixelBlock,
int32_t aBlockSize);
/// Checks if we have transparency, either because the header indicates that /// Checks if we have transparency, either because the header indicates that
/// there's alpha, or because the frame rect doesn't cover the entire image. /// there's alpha, or because the frame rect doesn't cover the entire image.