Bug 1279117 - Add SurfacePipe::WritePixelsToRow(). r=njn

This commit is contained in:
Seth Fowler 2016-06-10 14:44:24 -07:00
parent 1b06c54c9f
commit 020ad7a755
2 changed files with 373 additions and 34 deletions

View File

@ -166,42 +166,46 @@ public:
template <typename PixelType, typename Func>
WriteState WritePixels(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));
Maybe<WriteState> result;
while (!(result = DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc)))) { }
while (!IsSurfaceFinished()) {
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
return *result;
}
for (; mCol < mInputSize.width; ++mCol) {
NextPixel<PixelType> result = aFunc();
if (result.template is<PixelType>()) {
rowPtr[mCol] = result.template as<PixelType>();
continue;
}
switch (result.template as<WriteState>()) {
case WriteState::NEED_MORE_DATA:
return WriteState::NEED_MORE_DATA;
case WriteState::FINISHED:
ZeroOutRestOfSurface<PixelType>();
return WriteState::FINISHED;
case WriteState::FAILURE:
// Note that we don't need to record this anywhere, because this
// indicates an error in aFunc, and there's nothing wrong with our
// machinery. The caller can recover as needed and continue writing to
// the row.
return WriteState::FAILURE;
}
}
AdvanceRow(); // We've finished the row.
}
// We've finished the entire surface.
return WriteState::FINISHED;
/**
* 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.
* WritePixelsToRow() is completely memory safe.
*
* Writing continues until every pixel in the row has been written to. If the
* surface is complete at that pointer, WriteState::FINISHED is returned;
* otherwise, WritePixelsToRow() returns WriteState::NEED_MORE_DATA. The
* lambda can terminate writing early by returning a WriteState itself, which
* WritePixelsToRow() 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 the next
* pixel in the surface each time it's called. The lambda must
* return a NextPixel<PixelType> value.
*
* @return A WriteState value indicating the lambda generator's state.
* WritePixels() itself will return WriteState::FINISHED if writing
* the entire surface has finished, or WriteState::NEED_MORE_DATA if
* writing the row has finished, regardless of the lambda's internal
* state.
*/
template <typename PixelType, typename Func>
WriteState WritePixelsToRow(Func aFunc)
{
return DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc))
.valueOr(WriteState::NEED_MORE_DATA);
}
/**
@ -442,6 +446,60 @@ protected:
private:
/**
* An internal method used to implement both WritePixels() and
* WritePixelsToRow(). Those methods differ only in their behavior after a row
* is successfully written - WritePixels() continues to write another row,
* while WritePixelsToRow() returns to the caller. This method writes a single
* 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 WritePixels() and
* WritePixelsToRow() to implement their respective behaviors.
*/
template <typename PixelType, typename Func>
Maybe<WriteState> DoWritePixelsToRow(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);
for (; mCol < mInputSize.width; ++mCol) {
NextPixel<PixelType> result = aFunc();
if (result.template is<PixelType>()) {
rowPtr[mCol] = result.template as<PixelType>();
continue;
}
switch (result.template as<WriteState>()) {
case WriteState::NEED_MORE_DATA:
return Some(WriteState::NEED_MORE_DATA);
case WriteState::FINISHED:
ZeroOutRestOfSurface<PixelType>();
return Some(WriteState::FINISHED);
case WriteState::FAILURE:
// Note that we don't need to record this anywhere, because this
// indicates an error in aFunc, and there's nothing wrong with our
// machinery. The caller can recover as needed and continue writing to
// the row.
return Some(WriteState::FAILURE);
}
}
AdvanceRow(); // We've finished the row.
return IsSurfaceFinished() ? Some(WriteState::FINISHED)
: Nothing();
}
template <typename PixelType>
void ZeroOutRestOfSurface()
{
@ -546,6 +604,19 @@ public:
return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
}
/**
* 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.
* WritePixelsToRow() is completely memory safe.
*
* @see SurfaceFilter::WritePixelsToRow() for the canonical documentation.
*/
template <typename PixelType, typename Func>
WriteState WritePixelsToRow(Func aFunc)
{
return mHead->WritePixelsToRow<PixelType>(Forward<Func>(aFunc));
}
/**
* Write a row to the surface by copying from a buffer. This is bounds checked
* and memory safe with respect to the surface, but care must still be taken

View File

@ -310,6 +310,138 @@ TEST(ImageSurfaceSink, SurfaceSinkWritePixelsEarlyExit)
});
}
TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRow)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
// Write the first 99 rows of our 100x100 surface and verify that even
// though our lambda will yield pixels forever, only one row is written per
// call to WritePixelsToRow().
for (int row = 0; row < 99; ++row) {
uint32_t count = 0;
WriteState result = aSink->WritePixelsToRow<uint32_t>([&]{
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(100u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect);
CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, row + 1));
}
// Write the final line, which should finish the surface.
uint32_t count = 0;
WriteState result = aSink->WritePixelsToRow<uint32_t>([&]{
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
// Note that the final invalid rect we expect here is only the last row;
// that's because we called TakeInvalidRect() repeatedly in the loop above.
AssertCorrectPipelineFinalState(aSink,
IntRect(0, 99, 100, 1),
IntRect(0, 99, 100, 1));
// Check that the generated image is correct.
CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100));
// Attempt to write more and make sure that nothing gets written.
count = 0;
result = aSink->WritePixelsToRow<uint32_t>([&]{
count++;
return AsVariant(BGRAColor::Red().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100));
});
}
TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRowEarlyExit)
{
auto checkEarlyExit =
[](Decoder* aDecoder, SurfaceSink* aSink, WriteState aState) {
// Write half a row of green pixels and then exit early with |aState|. If
// the lambda keeps getting called, we'll write red pixels, which will cause
// the test to fail.
uint32_t count = 0;
auto result = aSink->WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
if (count == 50) {
return AsVariant(aState);
}
return count++ < 50 ? AsVariant(BGRAColor::Green().AsPixel())
: AsVariant(BGRAColor::Red().AsPixel());
});
EXPECT_EQ(aState, result);
EXPECT_EQ(50u, count);
CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1));
if (aState != WriteState::FINISHED) {
// We should still be able to write more at this point.
EXPECT_FALSE(aSink->IsSurfaceFinished());
// Verify that we can resume the same row and still stop at the end.
count = 0;
WriteState result = aSink->WritePixelsToRow<uint32_t>([&]{
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(50u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 1));
return;
}
// We should've finished the surface at this point.
AssertCorrectPipelineFinalState(aSink,
IntRect(0, 0, 100, 100),
IntRect(0, 0, 100, 100));
// Attempt to write more and make sure that nothing gets written.
count = 0;
result = aSink->WritePixelsToRow<uint32_t>([&]{
count++;
return AsVariant(BGRAColor::Red().AsPixel());
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1));
};
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, SurfaceSinkWriteBuffer)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
@ -996,6 +1128,142 @@ TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsEarlyExit)
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Write the first 99 rows of our 100x100 surface and verify that even
// though our lambda will yield pixels forever, only one row is written per
// call to WritePixelsToRow().
for (int row = 0; row < 99; ++row) {
uint32_t count = 0;
WriteState result = aSink->WritePixelsToRow<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(100u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect);
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, row + 1));
}
// Write the final line, which should finish the surface.
uint32_t count = 0;
WriteState result = aSink->WritePixelsToRow<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(100u, count);
// Note that the final invalid rect we expect here is only the last row;
// that's because we called TakeInvalidRect() repeatedly in the loop above.
AssertCorrectPipelineFinalState(aSink,
IntRect(0, 99, 100, 1),
IntRect(0, 99, 100, 1));
// Check that the generated image is correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
// Attempt to write more and make sure that nothing gets written.
count = 0;
result = aSink->WritePixelsToRow<uint8_t>([&]{
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRowEarlyExit)
{
auto checkEarlyExit =
[](Decoder* aDecoder, PalettedSurfaceSink* aSink, WriteState aState) {
// Write half a row of 255s and then exit early with |aState|. If the lambda
// keeps getting called, we'll write 128s, which will cause the test to
// fail.
uint32_t count = 0;
auto result = aSink->WritePixelsToRow<uint8_t>([&]() -> NextPixel<uint8_t> {
if (count == 50) {
return AsVariant(aState);
}
return count++ < 50 ? AsVariant(uint8_t(255))
: AsVariant(uint8_t(128));
});
EXPECT_EQ(aState, result);
EXPECT_EQ(50u, count);
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
if (aState != WriteState::FINISHED) {
// We should still be able to write more at this point.
EXPECT_FALSE(aSink->IsSurfaceFinished());
// Verify that we can resume the same row and still stop at the end.
count = 0;
WriteState result = aSink->WritePixelsToRow<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(50u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 1));
return;
}
// We should've finished the surface at this point.
AssertCorrectPipelineFinalState(aSink,
IntRect(0, 0, 100, 100),
IntRect(0, 0, 100, 100));
// Attempt to write more and make sure that nothing gets written.
count = 0;
result = aSink->WritePixelsToRow<uint8_t>([&]{
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
};
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBuffer)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),