mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 1279117 - Add SurfacePipe::WritePixelsToRow(). r=njn
This commit is contained in:
parent
1b06c54c9f
commit
020ad7a755
@ -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
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user