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 |