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 "GrDrawState.h" 11 #include "GrDrawTargetCaps.h" 12 #include "GrGpu.h" 13 14 #include "SkData.h" 15 #include "SkStrokeRec.h" 16 17 // TODO: try to remove this #include 18 #include "GrContext.h" 19 20 namespace { 21 22 /* 23 * Convert a boolean operation into a transfer mode code 24 */ 25 SkXfermode::Mode op_to_mode(SkRegion::Op op) { 26 27 static const SkXfermode::Mode modeMap[] = { 28 SkXfermode::kDstOut_Mode, // kDifference_Op 29 SkXfermode::kModulate_Mode, // kIntersect_Op 30 SkXfermode::kSrcOver_Mode, // kUnion_Op 31 SkXfermode::kXor_Mode, // kXOR_Op 32 SkXfermode::kClear_Mode, // kReverseDifference_Op 33 SkXfermode::kSrc_Mode, // kReplace_Op 34 }; 35 36 return modeMap[op]; 37 } 38 39 static inline GrPixelConfig fmt_to_config(SkTextureCompressor::Format fmt) { 40 41 GrPixelConfig config; 42 switch (fmt) { 43 case SkTextureCompressor::kLATC_Format: 44 config = kLATC_GrPixelConfig; 45 break; 46 47 case SkTextureCompressor::kR11_EAC_Format: 48 config = kR11_EAC_GrPixelConfig; 49 break; 50 51 case SkTextureCompressor::kASTC_12x12_Format: 52 config = kASTC_12x12_GrPixelConfig; 53 break; 54 55 case SkTextureCompressor::kETC1_Format: 56 config = kETC1_GrPixelConfig; 57 break; 58 59 default: 60 SkDEBUGFAIL("No GrPixelConfig for compression format!"); 61 // Best guess 62 config = kAlpha_8_GrPixelConfig; 63 break; 64 } 65 66 return config; 67 } 68 69 static bool choose_compressed_fmt(const GrDrawTargetCaps* caps, 70 SkTextureCompressor::Format *fmt) { 71 if (NULL == fmt) { 72 return false; 73 } 74 75 // We can't use scratch textures without the ability to update 76 // compressed textures... 77 if (!(caps->compressedTexSubImageSupport())) { 78 return false; 79 } 80 81 // Figure out what our preferred texture type is. If ASTC is available, that always 82 // gives the biggest win. Otherwise, in terms of compression speed and accuracy, 83 // LATC has a slight edge over R11 EAC. 84 if (caps->isConfigTexturable(kASTC_12x12_GrPixelConfig)) { 85 *fmt = SkTextureCompressor::kASTC_12x12_Format; 86 return true; 87 } else if (caps->isConfigTexturable(kLATC_GrPixelConfig)) { 88 *fmt = SkTextureCompressor::kLATC_Format; 89 return true; 90 } else if (caps->isConfigTexturable(kR11_EAC_GrPixelConfig)) { 91 *fmt = SkTextureCompressor::kR11_EAC_Format; 92 return true; 93 } 94 95 return false; 96 } 97 98 } 99 100 /** 101 * Draw a single rect element of the clip stack into the accumulation bitmap 102 */ 103 void GrSWMaskHelper::draw(const SkRect& rect, SkRegion::Op op, 104 bool antiAlias, uint8_t alpha) { 105 SkPaint paint; 106 107 SkXfermode* mode = SkXfermode::Create(op_to_mode(op)); 108 109 SkASSERT(kNone_CompressionMode == fCompressionMode); 110 111 paint.setXfermode(mode); 112 paint.setAntiAlias(antiAlias); 113 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); 114 115 fDraw.drawRect(rect, paint); 116 117 SkSafeUnref(mode); 118 } 119 120 /** 121 * Draw a single path element of the clip stack into the accumulation bitmap 122 */ 123 void GrSWMaskHelper::draw(const SkPath& path, const SkStrokeRec& stroke, SkRegion::Op op, 124 bool antiAlias, uint8_t alpha) { 125 126 SkPaint paint; 127 if (stroke.isHairlineStyle()) { 128 paint.setStyle(SkPaint::kStroke_Style); 129 paint.setStrokeWidth(SK_Scalar1); 130 } else { 131 if (stroke.isFillStyle()) { 132 paint.setStyle(SkPaint::kFill_Style); 133 } else { 134 paint.setStyle(SkPaint::kStroke_Style); 135 paint.setStrokeJoin(stroke.getJoin()); 136 paint.setStrokeCap(stroke.getCap()); 137 paint.setStrokeWidth(stroke.getWidth()); 138 } 139 } 140 paint.setAntiAlias(antiAlias); 141 142 SkTBlitterAllocator allocator; 143 SkBlitter* blitter = NULL; 144 if (kBlitter_CompressionMode == fCompressionMode) { 145 SkASSERT(fCompressedBuffer.get()); 146 blitter = SkTextureCompressor::CreateBlitterForFormat( 147 fBM.width(), fBM.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat); 148 } 149 150 if (SkRegion::kReplace_Op == op && 0xFF == alpha) { 151 SkASSERT(0xFF == paint.getAlpha()); 152 fDraw.drawPathCoverage(path, paint, blitter); 153 } else { 154 paint.setXfermodeMode(op_to_mode(op)); 155 paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha)); 156 fDraw.drawPath(path, paint, blitter); 157 } 158 } 159 160 bool GrSWMaskHelper::init(const SkIRect& resultBounds, 161 const SkMatrix* matrix, 162 bool allowCompression) { 163 if (matrix) { 164 fMatrix = *matrix; 165 } else { 166 fMatrix.setIdentity(); 167 } 168 169 // Now translate so the bound's UL corner is at the origin 170 fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1, 171 -resultBounds.fTop * SK_Scalar1); 172 SkIRect bounds = SkIRect::MakeWH(resultBounds.width(), 173 resultBounds.height()); 174 175 if (allowCompression && 176 fContext->getOptions().fDrawPathToCompressedTexture && 177 choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat)) { 178 fCompressionMode = kCompress_CompressionMode; 179 } 180 181 // Make sure that the width is a multiple of the desired block dimensions 182 // to allow for specialized SIMD instructions that compress multiple blocks at a time. 183 int cmpWidth = bounds.fRight; 184 int cmpHeight = bounds.fBottom; 185 if (kCompress_CompressionMode == fCompressionMode) { 186 int dimX, dimY; 187 SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY); 188 cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX); 189 cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY); 190 191 // Can we create a blitter? 192 if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) { 193 int cmpSz = SkTextureCompressor::GetCompressedDataSize( 194 fCompressedFormat, cmpWidth, cmpHeight); 195 196 SkASSERT(cmpSz > 0); 197 SkASSERT(NULL == fCompressedBuffer.get()); 198 fCompressedBuffer.reset(cmpSz); 199 fCompressionMode = kBlitter_CompressionMode; 200 } 201 } 202 203 // If we don't have a custom blitter, then we either need a bitmap to compress 204 // from or a bitmap that we're going to use as a texture. In any case, we should 205 // allocate the pixels for a bitmap 206 const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight); 207 if (kBlitter_CompressionMode != fCompressionMode) { 208 if (!fBM.tryAllocPixels(bmImageInfo)) { 209 return false; 210 } 211 212 sk_bzero(fBM.getPixels(), fBM.getSafeSize()); 213 } else { 214 // Otherwise, we just need to remember how big the buffer is... 215 fBM.setInfo(bmImageInfo); 216 } 217 218 sk_bzero(&fDraw, sizeof(fDraw)); 219 220 fRasterClip.setRect(bounds); 221 fDraw.fRC = &fRasterClip; 222 fDraw.fClip = &fRasterClip.bwRgn(); 223 fDraw.fMatrix = &fMatrix; 224 fDraw.fBitmap = &fBM; 225 return true; 226 } 227 228 /** 229 * Get a texture (from the texture cache) of the correct size & format. 230 * Return true on success; false on failure. 231 */ 232 bool GrSWMaskHelper::getTexture(GrAutoScratchTexture* texture) { 233 GrTextureDesc 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 texture->set(fContext, desc); 252 return SkToBool(texture->texture()); 253 } 254 255 void GrSWMaskHelper::sendTextureData(GrTexture *texture, const GrTextureDesc& desc, 256 const void *data, int 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 GrTextureDesc& 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 GrTextureDesc 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 /** 311 * Software rasterizes path to A8 mask (possibly using the context's matrix) 312 * and uploads the result to a scratch texture. Returns the resulting 313 * texture on success; NULL on failure. 314 */ 315 GrTexture* GrSWMaskHelper::DrawPathMaskToTexture(GrContext* context, 316 const SkPath& path, 317 const SkStrokeRec& stroke, 318 const SkIRect& resultBounds, 319 bool antiAlias, 320 SkMatrix* matrix) { 321 GrSWMaskHelper helper(context); 322 323 if (!helper.init(resultBounds, matrix)) { 324 return NULL; 325 } 326 327 helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF); 328 329 GrAutoScratchTexture ast; 330 if (!helper.getTexture(&ast)) { 331 return NULL; 332 } 333 334 helper.toTexture(ast.texture()); 335 336 return ast.detach(); 337 } 338 339 void GrSWMaskHelper::DrawToTargetWithPathMask(GrTexture* texture, 340 GrDrawTarget* target, 341 const SkIRect& rect) { 342 GrDrawState* drawState = target->drawState(); 343 344 GrDrawState::AutoViewMatrixRestore avmr; 345 if (!avmr.setIdentity(drawState)) { 346 return; 347 } 348 GrDrawState::AutoRestoreEffects are(drawState); 349 350 SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft, 351 SK_Scalar1 * rect.fTop, 352 SK_Scalar1 * rect.fRight, 353 SK_Scalar1 * rect.fBottom); 354 355 // We want to use device coords to compute the texture coordinates. We set our matrix to be 356 // equal to the view matrix followed by a translation so that the top-left of the device bounds 357 // maps to 0,0, and then a scaling matrix to normalized coords. We apply this matrix to the 358 // vertex positions rather than local coords. 359 SkMatrix maskMatrix; 360 maskMatrix.setIDiv(texture->width(), texture->height()); 361 maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop)); 362 maskMatrix.preConcat(drawState->getViewMatrix()); 363 364 drawState->addCoverageProcessor( 365 GrSimpleTextureEffect::Create(texture, 366 maskMatrix, 367 GrTextureParams::kNone_FilterMode, 368 kPosition_GrCoordSet))->unref(); 369 370 target->drawSimpleRect(dstRect); 371 } 372