Chromium Code Reviews| Index: src/utils/SkTextureCompressor_Blitter.h |
| diff --git a/src/utils/SkTextureCompressor_Blitter.h b/src/utils/SkTextureCompressor_Blitter.h |
| index 4470201b861d71c1de609c9546543ab36f2fbb03..cad3e20bd7b134621e68f80d79d88ae3b1ef5ab5 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 |
|
robertphillips
2014/08/11 19:16:22
Mv this ',' down to start of next line ?
krajcevski
2014/08/11 20:08:24
Done.
|
| - : 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 |