| Index: src/utils/SkTextureCompressor_Blitter.h
|
| diff --git a/src/utils/SkTextureCompressor_Blitter.h b/src/utils/SkTextureCompressor_Blitter.h
|
| index 4470201b861d71c1de609c9546543ab36f2fbb03..3bf10491b6dc608d18ceae3ef0306d266562f5c6 100644
|
| --- a/src/utils/SkTextureCompressor_Blitter.h
|
| +++ b/src/utils/SkTextureCompressor_Blitter.h
|
| @@ -13,6 +13,22 @@
|
|
|
| namespace SkTextureCompressor {
|
|
|
| +// Ostensibly, SkBlitter::BlitRect is supposed to set a rect of pixels to full
|
| +// alpha. This becomes problematic when using compressed texture blitters, since
|
| +// the rect rarely falls along block boundaries. The proper way to handle this is
|
| +// to update the compressed encoding of a block by resetting the proper parameters
|
| +// (and even recompressing the block) where a rect falls inbetween block boundaries.
|
| +// PEDANTIC_BLIT_RECT attempts to do this by requiring the struct passed to
|
| +// SkTCompressedAlphaBlitter to implement an UpdateBlock function call.
|
| +//
|
| +// However, the way that BlitRect gets used almost exclusively is to bracket inverse
|
| +// fills for paths. In other words, the top few rows and bottom few rows of a path
|
| +// that's getting inverse filled are called using blitRect. The rest are called using
|
| +// the standard blitAntiH. As a result, we can just call blitAntiH with a faux RLE
|
| +// of full alpha values, and then check in our flush() call that we don't run off the
|
| +// edge of the buffer. This is why we do not need this flag to be turned on.
|
| +#define PEDANTIC_BLIT_RECT 1
|
| +
|
| // This class implements a blitter that blits directly into a buffer that will
|
| // be used as an compressed alpha texture. We compute this buffer by
|
| // buffering scan lines and then outputting them all at once. The number of
|
| @@ -29,13 +45,17 @@ namespace SkTextureCompressor {
|
| //
|
| // // The function used to compress an A8 block. The layout of the
|
| // // block is also expected to be in row-major order.
|
| -// static void CompressA8Horizontal(uint8_t* dst, const uint8_t block[]);
|
| +// static void CompressA8Horizontal(uint8_t* dst, const uint8_t* src, int srcRowBytes);
|
| //
|
| +#if PEDANTIC_BLIT_RECT
|
| // // The function used to update an already compressed block. This will
|
| -// // most likely be implementation dependent.
|
| -// static void UpdateBlock(uint8_t* dst, const uint8_t* src);
|
| +// // most likely be implementation dependent. The mask variable will have
|
| +// // 0xFF in positions where the block should be updated and 0 in positions
|
| +// // where it shouldn't. src contains an uncompressed buffer of pixels.
|
| +// static void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
|
| +// const uint8_t* mask);
|
| +#endif
|
| // };
|
| -//
|
| template<int BlockDim, int EncodedBlockSize, typename CompressorType>
|
| class SkTCompressedAlphaBlitter : public SkBlitter {
|
| public:
|
| @@ -44,7 +64,8 @@ public:
|
| // debugging to make sure that we're properly setting the nextX distance
|
| // in flushRuns().
|
| #ifdef SK_DEBUG
|
| - : fBlitMaskCalled(false),
|
| + : fCalledOnceWithNonzeroY(false)
|
| + , fBlitMaskCalled(false),
|
| #else
|
| :
|
| #endif
|
| @@ -73,6 +94,8 @@ public:
|
| virtual void blitAntiH(int x, int y,
|
| const SkAlpha antialias[],
|
| const int16_t runs[]) SK_OVERRIDE {
|
| + SkASSERT(0 == x);
|
| +
|
| // Make sure that the new row to blit is either the first
|
| // row that we're blitting, or it's exactly the next scan row
|
| // since the last row that we blit. This is to ensure that when
|
| @@ -131,12 +154,122 @@ public:
|
| SkFAIL("Not implemented!");
|
| }
|
|
|
| - // Blit a solid rectangle one or more pixels wide.
|
| + // Blit a solid rectangle one or more pixels wide. It's assumed that blitRect
|
| + // is called as a way to bracket blitAntiH where above and below the path the
|
| + // called path just needs a solid rectangle to fill in the mask.
|
| +#ifdef SK_DEBUG
|
| + bool fCalledOnceWithNonzeroY;
|
| +#endif
|
| virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE {
|
| - // Analogous to blitRow, this function is intended for RGB targets
|
| - // and should never be called by this blitter. Any calls to this function
|
| - // are probably a bug and should be investigated.
|
| - SkFAIL("Not implemented!");
|
| +
|
| + // Assumptions:
|
| + SkASSERT(0 == x);
|
| + SkASSERT(width <= fWidth);
|
| +
|
| + // Make sure that we're only ever bracketing calls to blitAntiH.
|
| + SkASSERT((0 == y) || (!fCalledOnceWithNonzeroY && (fCalledOnceWithNonzeroY = true)));
|
| +
|
| +#if !(PEDANTIC_BLIT_RECT)
|
| + for (int i = 0; i < height; ++i) {
|
| + const SkAlpha kFullAlpha = 0xFF;
|
| + this->blitAntiH(x, y+i, &kFullAlpha, &kLongestRun);
|
| + }
|
| +#else
|
| + const int startBlockX = (x / BlockDim) * BlockDim;
|
| + const int startBlockY = (y / BlockDim) * BlockDim;
|
| +
|
| + const int endBlockX = ((x + width) / BlockDim) * BlockDim;
|
| + const int endBlockY = ((y + height) / BlockDim) * BlockDim;
|
| +
|
| + // If start and end are the same, then we only need to update a single block...
|
| + if (startBlockY == endBlockY && startBlockX == endBlockX) {
|
| + uint8_t mask[BlockDim*BlockDim];
|
| + memset(mask, 0, sizeof(mask));
|
| +
|
| + const int xoff = x - startBlockX;
|
| + SkASSERT((xoff + width) <= BlockDim);
|
| +
|
| + const int yoff = y - startBlockY;
|
| + SkASSERT((yoff + height) <= BlockDim);
|
| +
|
| + for (int j = 0; j < height; ++j) {
|
| + memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, width);
|
| + }
|
| +
|
| + uint8_t* dst = this->getBlock(startBlockX, startBlockY);
|
| + CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
|
| +
|
| + // If start and end are the same in the y dimension, then we can freely update an
|
| + // entire row of blocks...
|
| + } else if (startBlockY == endBlockY) {
|
| +
|
| + this->updateBlockRow(x, y, width, height, startBlockY, startBlockX, endBlockX);
|
| +
|
| + // Similarly, if the start and end are in the same column, then we can just update
|
| + // an entire column of blocks...
|
| + } else if (startBlockX == endBlockX) {
|
| +
|
| + this->updateBlockCol(x, y, width, height, startBlockX, startBlockY, endBlockY);
|
| +
|
| + // Otherwise, the rect spans a non-trivial region of blocks, and we have to construct
|
| + // a kind of 9-patch to update each of the pieces of the rect. The top and bottom
|
| + // rows are updated using updateBlockRow, and the left and right columns are updated
|
| + // using updateBlockColumn. Anything in the middle is simply memset to an opaque block
|
| + // encoding.
|
| + } else {
|
| +
|
| + const int innerStartBlockX = startBlockX + BlockDim;
|
| + const int innerStartBlockY = startBlockY + BlockDim;
|
| +
|
| + // Blit top row
|
| + const int topRowHeight = innerStartBlockY - y;
|
| + this->updateBlockRow(x, y, width, topRowHeight, startBlockY,
|
| + startBlockX, endBlockX);
|
| +
|
| + // Advance y
|
| + y += topRowHeight;
|
| + height -= topRowHeight;
|
| +
|
| + // Blit middle
|
| + if (endBlockY > innerStartBlockY) {
|
| +
|
| + // Update left row
|
| + this->updateBlockCol(x, y, innerStartBlockX - x, endBlockY, startBlockY,
|
| + startBlockX, innerStartBlockX);
|
| +
|
| + // Update the middle with an opaque encoding...
|
| + uint8_t mask[BlockDim*BlockDim];
|
| + memset(mask, 0xFF, sizeof(mask));
|
| +
|
| + uint8_t opaqueEncoding[EncodedBlockSize];
|
| + CompressorType::CompressA8Horizontal(opaqueEncoding, mask, BlockDim);
|
| +
|
| + for (int j = innerStartBlockY; j < endBlockY; j += BlockDim) {
|
| + uint8_t* opaqueDst = this->getBlock(innerStartBlockX, j);
|
| + for (int i = innerStartBlockX; i < endBlockX; i += BlockDim) {
|
| + memcpy(opaqueDst, opaqueEncoding, EncodedBlockSize);
|
| + opaqueDst += EncodedBlockSize;
|
| + }
|
| + }
|
| +
|
| + // If we need to update the right column, do that too
|
| + if (x + width > endBlockX) {
|
| + this->updateBlockCol(endBlockX, y, x + width - endBlockX, endBlockY,
|
| + endBlockX, innerStartBlockY, endBlockY);
|
| + }
|
| +
|
| + // Advance y
|
| + height = y + height - endBlockY;
|
| + y = endBlockY;
|
| + }
|
| +
|
| + // If we need to update the last row, then do that, too.
|
| + if (height > 0) {
|
| + this->updateBlockRow(x, y, width, height, endBlockY,
|
| + startBlockX, endBlockX);
|
| + }
|
| + }
|
| +#endif
|
| }
|
|
|
| // Blit a rectangle with one alpha-blended column on the left,
|
| @@ -400,6 +533,12 @@ private:
|
| // Make sure that we have a valid right-bound X value
|
| SkASSERT(finalX < 0xFFFFF);
|
|
|
| + // If the finalX is the longest run, then just blit until we have
|
| + // width...
|
| + if (kLongestRun == finalX) {
|
| + finalX = fWidth;
|
| + }
|
| +
|
| // Run the blitter...
|
| while (curX != finalX) {
|
| SkASSERT(finalX >= curX);
|
| @@ -449,19 +588,23 @@ private:
|
| SkASSERT(curX == finalX);
|
|
|
| // Figure out what the next advancement is...
|
| - for (int i = 0; i < BlockDim; ++i) {
|
| - if (nextX[i] == finalX) {
|
| - const int16_t run = *(fBufferedRuns[i].fRuns);
|
| - fBufferedRuns[i].fRuns += run;
|
| - fBufferedRuns[i].fAlphas += run;
|
| - curAlpha[i] = *(fBufferedRuns[i].fAlphas);
|
| - nextX[i] += *(fBufferedRuns[i].fRuns);
|
| + if (finalX < fWidth) {
|
| + for (int i = 0; i < BlockDim; ++i) {
|
| + if (nextX[i] == finalX) {
|
| + const int16_t run = *(fBufferedRuns[i].fRuns);
|
| + fBufferedRuns[i].fRuns += run;
|
| + fBufferedRuns[i].fAlphas += run;
|
| + curAlpha[i] = *(fBufferedRuns[i].fAlphas);
|
| + nextX[i] += *(fBufferedRuns[i].fRuns);
|
| + }
|
| }
|
| - }
|
|
|
| - finalX = 0xFFFFF;
|
| - for (int i = 0; i < BlockDim; ++i) {
|
| - finalX = SkMin32(nextX[i], finalX);
|
| + finalX = 0xFFFFF;
|
| + for (int i = 0; i < BlockDim; ++i) {
|
| + finalX = SkMin32(nextX[i], finalX);
|
| + }
|
| + } else {
|
| + curX = finalX;
|
| }
|
| }
|
|
|
| @@ -483,6 +626,102 @@ private:
|
|
|
| fNextRun = 0;
|
| }
|
| +
|
| +#if PEDANTIC_BLIT_RECT
|
| + void updateBlockRow(int x, int y, int width, int height,
|
| + int blockRow, int startBlockX, int endBlockX) {
|
| + if (0 == width || 0 == height || startBlockX == endBlockX) {
|
| + return;
|
| + }
|
| +
|
| + uint8_t* dst = this->getBlock(startBlockX, BlockDim * (y / BlockDim));
|
| +
|
| + // One horizontal strip to update
|
| + uint8_t mask[BlockDim*BlockDim];
|
| + memset(mask, 0, sizeof(mask));
|
| +
|
| + // Update the left cap
|
| + int blockX = startBlockX;
|
| + const int yoff = y - blockRow;
|
| + for (int j = 0; j < height; ++j) {
|
| + const int xoff = x - blockX;
|
| + memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, BlockDim - xoff);
|
| + }
|
| + CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
|
| + dst += EncodedBlockSize;
|
| + blockX += BlockDim;
|
| +
|
| + // Update the middle
|
| + if (blockX < endBlockX) {
|
| + for (int j = 0; j < height; ++j) {
|
| + memset(mask + (j + yoff)*BlockDim, 0xFF, BlockDim);
|
| + }
|
| + while (blockX < endBlockX) {
|
| + CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
|
| + dst += EncodedBlockSize;
|
| + blockX += BlockDim;
|
| + }
|
| + }
|
| +
|
| + SkASSERT(endBlockX == blockX);
|
| +
|
| + // Update the right cap (if we need to)
|
| + if (x + width > endBlockX) {
|
| + memset(mask, 0, sizeof(mask));
|
| + for (int j = 0; j < height; ++j) {
|
| + const int xoff = (x+width-blockX);
|
| + memset(mask + (j+yoff)*BlockDim, 0xFF, xoff);
|
| + }
|
| + CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
|
| + }
|
| + }
|
| +
|
| + void updateBlockCol(int x, int y, int width, int height,
|
| + int blockCol, int startBlockY, int endBlockY) {
|
| + if (0 == width || 0 == height || startBlockY == endBlockY) {
|
| + return;
|
| + }
|
| +
|
| + // One vertical strip to update
|
| + uint8_t mask[BlockDim*BlockDim];
|
| + memset(mask, 0, sizeof(mask));
|
| + const int maskX0 = x - blockCol;
|
| + const int maskWidth = maskX0 + width;
|
| + SkASSERT(maskWidth <= BlockDim);
|
| +
|
| + // Update the top cap
|
| + int blockY = startBlockY;
|
| + for (int j = (y - blockY); j < BlockDim; ++j) {
|
| + memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
|
| + }
|
| + CompressorType::UpdateBlock(this->getBlock(blockCol, blockY), mask, BlockDim, mask);
|
| + blockY += BlockDim;
|
| +
|
| + // Update middle
|
| + if (blockY < endBlockY) {
|
| + for (int j = 0; j < BlockDim; ++j) {
|
| + memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
|
| + }
|
| + while (blockY < endBlockY) {
|
| + CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
|
| + mask, BlockDim, mask);
|
| + blockY += BlockDim;
|
| + }
|
| + }
|
| +
|
| + SkASSERT(endBlockY == blockY);
|
| +
|
| + // Update bottom
|
| + if (y + height > endBlockY) {
|
| + for (int j = y+height; j < endBlockY + BlockDim; ++j) {
|
| + memset(mask + (j-endBlockY)*BlockDim, 0, BlockDim);
|
| + }
|
| + CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
|
| + mask, BlockDim, mask);
|
| + }
|
| + }
|
| +#endif // PEDANTIC_BLIT_RECT
|
| +
|
| };
|
|
|
| } // namespace SkTextureCompressor
|
|
|