robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2015 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 "GrBlurUtils.h" |
| 9 | #include "GrDrawContext.h" |
bsalomon | 7622863 | 2015-05-29 08:02:10 -0700 | [diff] [blame] | 10 | #include "GrCaps.h" |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 11 | #include "GrContext.h" |
| 12 | #include "effects/GrSimpleTextureEffect.h" |
| 13 | #include "GrStrokeInfo.h" |
| 14 | #include "GrTexture.h" |
| 15 | #include "GrTextureProvider.h" |
| 16 | #include "SkDraw.h" |
bsalomon | f1b7a1d | 2015-09-28 06:26:28 -0700 | [diff] [blame] | 17 | #include "SkGrPriv.h" |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 18 | #include "SkMaskFilter.h" |
| 19 | #include "SkPaint.h" |
| 20 | |
| 21 | static bool clip_bounds_quick_reject(const SkIRect& clipBounds, const SkIRect& rect) { |
| 22 | return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect); |
| 23 | } |
| 24 | |
| 25 | // Draw a mask using the supplied paint. Since the coverage/geometry |
| 26 | // is already burnt into the mask this boils down to a rect draw. |
| 27 | // Return true if the mask was successfully drawn. |
| 28 | static bool draw_mask(GrDrawContext* drawContext, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 29 | const GrClip& clip, |
| 30 | const SkMatrix& viewMatrix, |
| 31 | const SkRect& maskRect, |
| 32 | GrPaint* grp, |
| 33 | GrTexture* mask) { |
| 34 | SkMatrix matrix; |
| 35 | matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop); |
| 36 | matrix.postIDiv(mask->width(), mask->height()); |
| 37 | |
bsalomon | 4a33952 | 2015-10-06 08:40:50 -0700 | [diff] [blame] | 38 | grp->addCoverageFragmentProcessor(GrSimpleTextureEffect::Create(mask, matrix, |
| 39 | kDevice_GrCoordSet))->unref(); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 40 | |
| 41 | SkMatrix inverse; |
| 42 | if (!viewMatrix.invert(&inverse)) { |
| 43 | return false; |
| 44 | } |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 45 | drawContext->drawNonAARectWithLocalMatrix(clip, *grp, SkMatrix::I(), maskRect, inverse); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 46 | return true; |
| 47 | } |
| 48 | |
| 49 | static bool draw_with_mask_filter(GrDrawContext* drawContext, |
| 50 | GrTextureProvider* textureProvider, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 51 | const GrClip& clipData, |
| 52 | const SkMatrix& viewMatrix, |
| 53 | const SkPath& devPath, |
| 54 | SkMaskFilter* filter, |
| 55 | const SkIRect& clipBounds, |
| 56 | GrPaint* grp, |
| 57 | SkPaint::Style style) { |
| 58 | SkMask srcM, dstM; |
| 59 | |
| 60 | if (!SkDraw::DrawToMask(devPath, &clipBounds, filter, &viewMatrix, &srcM, |
| 61 | SkMask::kComputeBoundsAndRenderImage_CreateMode, style)) { |
| 62 | return false; |
| 63 | } |
| 64 | SkAutoMaskFreeImage autoSrc(srcM.fImage); |
| 65 | |
halcanary | 96fcdcc | 2015-08-27 07:41:13 -0700 | [diff] [blame] | 66 | if (!filter->filterMask(&dstM, srcM, viewMatrix, nullptr)) { |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 67 | return false; |
| 68 | } |
| 69 | // this will free-up dstM when we're done (allocated in filterMask()) |
| 70 | SkAutoMaskFreeImage autoDst(dstM.fImage); |
| 71 | |
| 72 | if (clip_bounds_quick_reject(clipBounds, dstM.fBounds)) { |
| 73 | return false; |
| 74 | } |
| 75 | |
| 76 | // we now have a device-aligned 8bit mask in dstM, ready to be drawn using |
| 77 | // the current clip (and identity matrix) and GrPaint settings |
| 78 | GrSurfaceDesc desc; |
| 79 | desc.fWidth = dstM.fBounds.width(); |
| 80 | desc.fHeight = dstM.fBounds.height(); |
| 81 | desc.fConfig = kAlpha_8_GrPixelConfig; |
| 82 | |
bsalomon | eae6200 | 2015-07-31 13:59:30 -0700 | [diff] [blame] | 83 | SkAutoTUnref<GrTexture> texture(textureProvider->createApproxTexture(desc)); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 84 | if (!texture) { |
| 85 | return false; |
| 86 | } |
| 87 | texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, |
| 88 | dstM.fImage, dstM.fRowBytes); |
| 89 | |
| 90 | SkRect maskRect = SkRect::Make(dstM.fBounds); |
| 91 | |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 92 | return draw_mask(drawContext, clipData, viewMatrix, maskRect, grp, texture); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | // Create a mask of 'devPath' and place the result in 'mask'. |
| 96 | static GrTexture* create_mask_GPU(GrContext* context, |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 97 | SkRect* maskRect, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 98 | const SkPath& devPath, |
| 99 | const GrStrokeInfo& strokeInfo, |
| 100 | bool doAA, |
| 101 | int sampleCnt) { |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 102 | // This mask will ultimately be drawn as a non-AA rect (see draw_mask). |
| 103 | // Non-AA rects have a bad habit of snapping arbitrarily. Integerize here |
| 104 | // so the mask draws in a reproducible manner. |
| 105 | *maskRect = SkRect::Make(maskRect->roundOut()); |
| 106 | |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 107 | GrSurfaceDesc desc; |
| 108 | desc.fFlags = kRenderTarget_GrSurfaceFlag; |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 109 | desc.fWidth = SkScalarCeilToInt(maskRect->width()); |
| 110 | desc.fHeight = SkScalarCeilToInt(maskRect->height()); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 111 | desc.fSampleCnt = doAA ? sampleCnt : 0; |
| 112 | // We actually only need A8, but it often isn't supported as a |
| 113 | // render target so default to RGBA_8888 |
| 114 | desc.fConfig = kRGBA_8888_GrPixelConfig; |
| 115 | |
bsalomon | 7622863 | 2015-05-29 08:02:10 -0700 | [diff] [blame] | 116 | if (context->caps()->isConfigRenderable(kAlpha_8_GrPixelConfig, desc.fSampleCnt > 0)) { |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 117 | desc.fConfig = kAlpha_8_GrPixelConfig; |
| 118 | } |
| 119 | |
bsalomon | eae6200 | 2015-07-31 13:59:30 -0700 | [diff] [blame] | 120 | GrTexture* mask = context->textureProvider()->createApproxTexture(desc); |
halcanary | 96fcdcc | 2015-08-27 07:41:13 -0700 | [diff] [blame] | 121 | if (nullptr == mask) { |
| 122 | return nullptr; |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 123 | } |
| 124 | |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 125 | SkRect clipRect = SkRect::MakeWH(maskRect->width(), maskRect->height()); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 126 | |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 127 | SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(mask->asRenderTarget())); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 128 | if (!drawContext) { |
halcanary | 96fcdcc | 2015-08-27 07:41:13 -0700 | [diff] [blame] | 129 | return nullptr; |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 130 | } |
| 131 | |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 132 | drawContext->clear(nullptr, 0x0, true); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 133 | |
| 134 | GrPaint tempPaint; |
| 135 | tempPaint.setAntiAlias(doAA); |
| 136 | tempPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op); |
| 137 | |
| 138 | // setup new clip |
| 139 | GrClip clip(clipRect); |
| 140 | |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 141 | // Draw the mask into maskTexture with the path's integerized top-left at |
| 142 | // the origin using tempPaint. |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 143 | SkMatrix translate; |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 144 | translate.setTranslate(-maskRect->fLeft, -maskRect->fTop); |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 145 | drawContext->drawPath(clip, tempPaint, translate, devPath, strokeInfo); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 146 | return mask; |
| 147 | } |
| 148 | |
| 149 | void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, |
| 150 | GrDrawContext* drawContext, |
| 151 | GrRenderTarget* renderTarget, |
| 152 | const GrClip& clip, |
| 153 | const SkPath& origSrcPath, |
| 154 | const SkPaint& paint, |
| 155 | const SkMatrix& origViewMatrix, |
| 156 | const SkMatrix* prePathMatrix, |
| 157 | const SkIRect& clipBounds, |
| 158 | bool pathIsMutable) { |
| 159 | SkASSERT(!pathIsMutable || origSrcPath.isVolatile()); |
| 160 | |
| 161 | GrStrokeInfo strokeInfo(paint); |
| 162 | |
| 163 | // If we have a prematrix, apply it to the path, optimizing for the case |
| 164 | // where the original path can in fact be modified in place (even though |
| 165 | // its parameter type is const). |
| 166 | SkPath* pathPtr = const_cast<SkPath*>(&origSrcPath); |
| 167 | SkTLazy<SkPath> tmpPath; |
| 168 | SkTLazy<SkPath> effectPath; |
| 169 | SkPathEffect* pathEffect = paint.getPathEffect(); |
| 170 | |
| 171 | SkMatrix viewMatrix = origViewMatrix; |
| 172 | |
| 173 | if (prePathMatrix) { |
| 174 | // stroking, path effects, and blurs are supposed to be applied *after* the prePathMatrix. |
| 175 | // The pre-path-matrix also should not affect shading. |
halcanary | 96fcdcc | 2015-08-27 07:41:13 -0700 | [diff] [blame] | 176 | if (nullptr == paint.getMaskFilter() && nullptr == pathEffect && nullptr == paint.getShader() && |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 177 | (strokeInfo.isFillStyle() || strokeInfo.isHairlineStyle())) { |
| 178 | viewMatrix.preConcat(*prePathMatrix); |
| 179 | } else { |
| 180 | SkPath* result = pathPtr; |
| 181 | |
| 182 | if (!pathIsMutable) { |
| 183 | result = tmpPath.init(); |
| 184 | result->setIsVolatile(true); |
| 185 | pathIsMutable = true; |
| 186 | } |
| 187 | // should I push prePathMatrix on our MV stack temporarily, instead |
| 188 | // of applying it here? See SkDraw.cpp |
| 189 | pathPtr->transform(*prePathMatrix, result); |
| 190 | pathPtr = result; |
| 191 | } |
| 192 | } |
| 193 | // at this point we're done with prePathMatrix |
| 194 | SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;) |
| 195 | |
| 196 | GrPaint grPaint; |
bsalomon | f1b7a1d | 2015-09-28 06:26:28 -0700 | [diff] [blame] | 197 | if (!SkPaintToGrPaint(context, paint, viewMatrix, &grPaint)) { |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 198 | return; |
| 199 | } |
| 200 | |
halcanary | 96fcdcc | 2015-08-27 07:41:13 -0700 | [diff] [blame] | 201 | const SkRect* cullRect = nullptr; // TODO: what is our bounds? |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 202 | if (!strokeInfo.isDashed() && pathEffect && pathEffect->filterPath(effectPath.init(), *pathPtr, |
| 203 | &strokeInfo, cullRect)) { |
| 204 | pathPtr = effectPath.get(); |
| 205 | pathIsMutable = true; |
| 206 | } |
| 207 | |
| 208 | if (paint.getMaskFilter()) { |
| 209 | if (!strokeInfo.isHairlineStyle()) { |
| 210 | SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init(); |
| 211 | if (strokeInfo.isDashed()) { |
| 212 | if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) { |
| 213 | pathPtr = strokedPath; |
| 214 | pathIsMutable = true; |
| 215 | } |
| 216 | strokeInfo.removeDash(); |
| 217 | } |
| 218 | if (strokeInfo.applyToPath(strokedPath, *pathPtr)) { |
| 219 | pathPtr = strokedPath; |
| 220 | pathIsMutable = true; |
| 221 | strokeInfo.setFillStyle(); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // avoid possibly allocating a new path in transform if we can |
| 226 | SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init(); |
| 227 | if (!pathIsMutable) { |
| 228 | devPathPtr->setIsVolatile(true); |
| 229 | } |
| 230 | |
| 231 | // transform the path into device space |
| 232 | pathPtr->transform(viewMatrix, devPathPtr); |
| 233 | |
| 234 | SkRect maskRect; |
robertphillips | 30c4cae | 2015-09-15 10:20:55 -0700 | [diff] [blame] | 235 | if (paint.getMaskFilter()->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()), |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 236 | clipBounds, |
| 237 | viewMatrix, |
| 238 | &maskRect)) { |
| 239 | SkIRect finalIRect; |
| 240 | maskRect.roundOut(&finalIRect); |
| 241 | if (clip_bounds_quick_reject(clipBounds, finalIRect)) { |
| 242 | // clipped out |
| 243 | return; |
| 244 | } |
| 245 | |
robertphillips | ff0ca5e | 2015-07-22 11:54:44 -0700 | [diff] [blame] | 246 | if (paint.getMaskFilter()->directFilterMaskGPU(context->textureProvider(), |
| 247 | drawContext, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 248 | &grPaint, |
| 249 | clip, |
| 250 | viewMatrix, |
| 251 | strokeInfo, |
| 252 | *devPathPtr)) { |
| 253 | // the mask filter was able to draw itself directly, so there's nothing |
| 254 | // left to do. |
| 255 | return; |
| 256 | } |
| 257 | |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 258 | SkAutoTUnref<GrTexture> mask(create_mask_GPU(context, |
robertphillips | 3833daa | 2015-09-14 11:18:13 -0700 | [diff] [blame] | 259 | &maskRect, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 260 | *devPathPtr, |
| 261 | strokeInfo, |
| 262 | grPaint.isAntiAlias(), |
vbuzinov | dded696 | 2015-06-12 08:59:45 -0700 | [diff] [blame] | 263 | renderTarget->numColorSamples())); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 264 | if (mask) { |
| 265 | GrTexture* filtered; |
| 266 | |
| 267 | if (paint.getMaskFilter()->filterMaskGPU(mask, viewMatrix, maskRect, |
| 268 | &filtered, true)) { |
| 269 | // filterMaskGPU gives us ownership of a ref to the result |
| 270 | SkAutoTUnref<GrTexture> atu(filtered); |
| 271 | if (draw_mask(drawContext, |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 272 | clip, |
| 273 | viewMatrix, |
| 274 | maskRect, |
| 275 | &grPaint, |
| 276 | filtered)) { |
| 277 | // This path is completely drawn |
| 278 | return; |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | // draw the mask on the CPU - this is a fallthrough path in case the |
| 285 | // GPU path fails |
| 286 | SkPaint::Style style = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style : |
| 287 | SkPaint::kFill_Style; |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 288 | draw_with_mask_filter(drawContext, context->textureProvider(), |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 289 | clip, viewMatrix, *devPathPtr, |
| 290 | paint.getMaskFilter(), clipBounds, &grPaint, style); |
| 291 | return; |
| 292 | } |
| 293 | |
robertphillips | 2e1e51f | 2015-10-15 08:01:48 -0700 | [diff] [blame] | 294 | drawContext->drawPath(clip, grPaint, viewMatrix, *pathPtr, strokeInfo); |
robertphillips | ccb1b57 | 2015-05-27 11:02:55 -0700 | [diff] [blame] | 295 | } |
| 296 | |