1 /* 2 * Copyright 2012 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "GrSWMaskHelper.h" 9 10 #include "GrPipelineBuilder.h" 11 #include "GrDrawTargetCaps.h" 12 #include "GrGpu.h" 13 14 #include "SkData.h" 15 #include "SkDistanceFieldGen.h" 16 #include "SkStrokeRec.h" 17 18 // TODO: try to remove this #include 19 #include "GrContext.h" 20 21 namespace { 22 23 /* 24 * Convert a boolean operation into a transfer mode code 25 */ 26 SkXfermode::Mode op_to_mode(SkRegion::Op op) { 27 28 static const SkXfermode::Mode modeMap[] = { 29 SkXfermode::kDstOut_Mode, // kDifference_Op 30 SkXfermode::kModulate_Mode, // kIntersect_Op 31 SkXfermode::kSrcOver_Mode, // kUnion_Op 32 SkXfermode::kXor_Mode, // kXOR_Op 33 SkXfermode::kClear_Mode, // kReverseDifference_Op 34 SkXfermode::kSrc_Mode, // kReplace_Op 35 }; 36 37 return modeMap[op]; 38 } 39 40 static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) { 41 42 GrPixelConfig config; 43 switch (fmt) { 44 case SkTextureCompressor::kLATC_Format: 45 config = kLATC_GrPixelConfig; 46 break; 47 48 case SkTextureCompressor::kR11_EAC_Format: 49 config = kR11_EAC_GrPixelConfig; 50 break; 51 52 case SkTextureCompressor::kASTC_12x12_Format: 53 config = kASTC_12x12_GrPixelConfig; 54 break; 55 56 case SkTextureCompressor::kETC1_Format: 57 config = kETC1_GrPixelConfig; 58 break; 59 60 default: 61 SkDEBUGFAIL("No GrPixelConfig for compression format!"); 62 // Best guess 63 config = kAlpha_8_GrPixelConfig; 64 break; 65 } 66 67 return config; 68 } 69 70 static bool choose_compressed_fmt(const GrDrawTargetCaps* caps, 71 SkTextureCompressor::Format *fmt) { 72 if (NULL == fmt) { 73 return false; 74 } 75 76 // We can't use scratch textures without the ability to update 77 // compressed textures... 78 if (!(caps->compressedTexSubImageSupport())) { 79 return false; 80 } 81 82 // Figure out what our preferred texture type is. If ASTC is available, that always 83 // gives the biggest win. Otherwise, in terms of compression speed and accuracy, 84 // LATC has a slight edge over R11 EAC. 85 if (caps->isConfigTexturable(kASTC_12x12_GrPixelConfig)) { 86 *fmt = SkTextureCompressor::kASTC_12x12_Format; 87 return true; 88 } else if (caps->isConfigTexturable(kLATC_GrPixelConfig)) { 89 *fmt = SkTextureCompressor::kLATC_Format; 90 return true; 91 } else if (caps->isConfigTexturable(kR11_EAC_GrPixelConfig)) { 92 *fmt = SkTextureCompressor::kR11_EAC_Format; 93 return true; 94 } 95 96 return false; 97 } 98 99 } 100 101 /** 102 * Draw a single rect element of the clip stack into the accumulation bitmap 103 */ 104 void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op, 105 bool antiAlias, uint8_t alpha) { 106 SkPaint paint; 107 108 SkXfermode* mode = SkXfermode::Create(op_to_mode(op)); 109 110 SkASSERT(kNone_CompressionMode == fCompressionMode); 111 112 paint.setXfermode(mode); 113 paint.setAntiAlias(antiAlias); 114 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); 115 116 fDraw.drawRect(rect, paint); 117 118 SkSafeUnref(mode); 119 } 120 121 /** 122 * Draw a single path element of the clip stack into the accumulation bitmap 123 */ 124 void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op, 125 bool antiAlias, uint8_t alpha) { 126 127 SkPaint paint; 128 if (stroke.isHairlineStyle()) { 129 paint.setStyle(SkPaint::kStroke_Style); 130 paint.setStrokeWidth(SK_Scalar1); 131 } else { 132 if (stroke.isFillStyle()) { 133 paint.setStyle(SkPaint::kFill_Style); 134 } else { 135 paint.setStyle(SkPaint::kStroke_Style); 136 paint.setStrokeJoin(stroke.getJoin()); 137 paint.setStrokeCap(stroke.getCap()); 138 paint.setStrokeWidth(stroke.getWidth()); 139 } 140 } 141 paint.setAntiAlias(antiAlias); 142 143 SkTBlitterAllocator allocator; 144 SkBlitter* blitter = NULL; 145 if (kBlitter_CompressionMode == fCompressionMode) { 146 SkASSERT(fCompressedBuffer.get()); 147 blitter = SkTextureCompressor::CreateBlitterForFormat( 148 fBM.width(), fBM.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat); 149 } 150 151 if (SkRegion::kReplace_Op == op && 0xFF == alpha) { 152 SkASSERT(0xFF == paint.getAlpha()); 153 fDraw.drawPathCoverage(path, paint, blitter); 154 } else { 155 paint.setXfermodeMode(op_to_mode(op)); 156 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); 157 fDraw.drawPath(path, paint, blitter); 158 } 159 } 160 161 bool GrSWMaskHelper::init(const SkIRect& resultBounds, 162 const SkMatrix* matrix, 163 bool allowCompression) { 164 if (matrix) { 165 fMatrix = *matrix; 166 } else { 167 fMatrix.setIdentity(); 168 } 169 170 // Now translate so the bound's UL corner is at the origin 171 fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1, 172 -resultBounds.fTop * SK_Scalar1); 173 SkIRect bounds = SkIRect::MakeWH(resultBounds.width(), 174 resultBounds.height()); 175 176 if (allowCompression && 177 fContext->getOptions().fDrawPathToCompressedTexture && 178 choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat)) { 179 fCompressionMode = kCompress_CompressionMode; 180 } 181 182 // Make sure that the width is a multiple of the desired block dimensions 183 // to allow for specialized SIMD instructions that compress multiple blocks at a time. 184 int cmpWidth = bounds.fRight; 185 int cmpHeight = bounds.fBottom; 186 if (kCompress_CompressionMode == fCompressionMode) { 187 int dimX, dimY; 188 SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY); 189 cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX); 190 cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY); 191 192 // Can we create a blitter? 193 if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) { 194 int cmpSz = SkTextureCompressor::GetCompressedDataSize( 195 fCompressedFormat, cmpWidth, cmpHeight); 196 197 SkASSERT(cmpSz > 0); 198 SkASSERT(NULL == fCompressedBuffer.get()); 199 fCompressedBuffer.reset(cmpSz); 200 fCompressionMode = kBlitter_CompressionMode; 201 } 202 } 203 204 // If we don't have a custom blitter, then we either need a bitmap to compress 205 // from or a bitmap that we're going to use as a texture. In any case, we should 206 // allocate the pixels for a bitmap 207 const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight); 208 if (kBlitter_CompressionMode != fCompressionMode) { 209 if (!fBM.tryAllocPixels(bmImageInfo)) { 210 return false; 211 } 212 213 sk_bzero(fBM.getPixels(), fBM.getSafeSize()); 214 } else { 215 // Otherwise, we just need to remember how big the buffer is... 216 fBM.setInfo(bmImageInfo); 217 } 218 219 sk_bzero(&fDraw, sizeof(fDraw)); 220 221 fRasterClip.setRect(bounds); 222 fDraw.fRC = &fRasterClip; 223 fDraw.fClip = &fRasterClip.bwRgn(); 224 fDraw.fMatrix = &fMatrix; 225 fDraw.fBitmap = &fBM; 226 return true; 227 } 228 229 /** 230 * Get a texture (from the texture cache) of the correct size & format. 231 */ 232 GrTexture* GrSWMaskHelper::createTexture() { 233 GrSurfaceDesc desc; 234 desc.fWidth = fBM.width(); 235 desc.fHeight = fBM.height(); 236 desc.fConfig = kAlpha_8_GrPixelConfig; 237 238 if (kNone_CompressionMode != fCompressionMode) { 239 240 #ifdef SK_DEBUG 241 int dimX, dimY; 242 SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY); 243 SkASSERT((desc.fWidth % dimX) == 0); 244 SkASSERT((desc.fHeight % dimY) == 0); 245 #endif 246 247 desc.fConfig = fmt_to_config(fCompressedFormat); 248 SkASSERT(fContext->getGpu()->caps()->isConfigTexturable(desc.fConfig)); 249 } 250 251 return fContext->textureProvider()->refScratchTexture( 252 desc, GrTextureProvider::kApprox_ScratchTexMatch); 253 } 254 255 void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrSurfaceDesc& desc, 256 const void *data, size_t rowbytes) { 257 // If we aren't reusing scratch textures we don't need to flush before 258 // writing since no one else will be using 'texture' 259 bool reuseScratch = fContext->getGpu()->caps()->reuseScratchTextures(); 260 261 // Since we're uploading to it, and it's compressed, 'texture' shouldn't 262 // have a render target. 263 SkASSERT(NULL == texture->asRenderTarget()); 264 265 texture->writePixels(0, 0, desc.fWidth, desc.fHeight, 266 desc.fConfig, data, rowbytes, 267 reuseScratch ? 0 : GrContext::kDontFlush_PixelOpsFlag); 268 } 269 270 void GrSWMaskHelper::compressTextureData(GrTexture *texture, const GrSurfaceDesc& desc) { 271 272 SkASSERT(GrPixelConfigIsCompressed(desc.fConfig)); 273 SkASSERT(fmt_to_config(fCompressedFormat) == desc.fConfig); 274 275 SkAutoDataUnref cmpData(SkTextureCompressor::CompressBitmapToFormat(fBM, fCompressedFormat)); 276 SkASSERT(cmpData); 277 278 this->sendTextureData(texture, desc, cmpData->data(), 0); 279 } 280 281 /** 282 * Move the result of the software mask generation back to the gpu 283 */ 284 void GrSWMaskHelper::toTexture(GrTexture *texture) { 285 SkAutoLockPixels alp(fBM); 286 287 GrSurfaceDesc desc; 288 desc.fWidth = fBM.width(); 289 desc.fHeight = fBM.height(); 290 desc.fConfig = texture->config(); 291 292 // First see if we should compress this texture before uploading. 293 switch (fCompressionMode) { 294 case kNone_CompressionMode: 295 this->sendTextureData(texture, desc, fBM.getPixels(), fBM.rowBytes()); 296 break; 297 298 case kCompress_CompressionMode: 299 this->compressTextureData(texture, desc); 300 break; 301 302 case kBlitter_CompressionMode: 303 SkASSERT(fCompressedBuffer.get()); 304 this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0); 305 break; 306 } 307 } 308 309 /** 310 * Convert mask generation results to a signed distance field 311 */ 312 void GrSWMaskHelper::toSDF(unsigned char* sdf) { 313 SkAutoLockPixels alp(fBM); 314 315 SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fBM.getPixels(), 316 fBM.width(), fBM.height(), fBM.rowBytes()); 317 } 318 319 //////////////////////////////////////////////////////////////////////////////// 320 /** 321 * Software rasterizes path to A8 mask (possibly using the context's matrix) 322 * and uploads the result to a scratch texture. Returns the resulting 323 * texture on success; NULL on failure. 324 */ 325 GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context, 326 const SkPath& path, 327 const SkStrokeRec& stroke, 328 const SkIRect& resultBounds, 329 bool antiAlias, 330 const SkMatrix* matrix) { 331 GrSWMaskHelper helper(context); 332 333 if (!helper.init(resultBounds, matrix)) { 334 return NULL; 335 } 336 337 helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF); 338 339 GrTexture* texture(helper.createTexture()); 340 if (!texture) { 341 return NULL; 342 } 343 344 helper.toTexture(texture); 345 346 return texture; 347 } 348 349 void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture, 350 GrDrawTarget* target, 351 GrPipelineBuilder* pipelineBuilder, 352 GrColor color, 353 const SkMatrix& viewMatrix, 354 const SkIRect& rect) { 355 SkMatrix invert; 356 if (!viewMatrix.invert(&invert)) { 357 return; 358 } 359 GrPipelineBuilder::AutoRestoreFragmentProcessors arfp(pipelineBuilder); 360 361 SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft, 362 SK_Scalar1 * rect.fTop, 363 SK_Scalar1 * rect.fRight, 364 SK_Scalar1 * rect.fBottom); 365 366 // We use device coords to compute the texture coordinates. We take the device coords and apply 367 // a translation so that the top-left of the device bounds maps to 0,0, and then a scaling 368 // matrix to normalized coords. 369 SkMatrix maskMatrix; 370 maskMatrix.setIDiv(texture->width(), texture->height()); 371 maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop)); 372 373 pipelineBuilder->addCoverageProcessor( 374 GrSimpleTextureEffect::Create(texture, 375 maskMatrix, 376 GrTextureParams::kNone_FilterMode, 377 kDevice_GrCoordSet))->unref(); 378 379 target->drawRect(pipelineBuilder, color, SkMatrix::I(), dstRect, NULL, &invert); 380 } 381