1 /* 2 * Copyright 2016 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 "GrClipStackClip.h" 9 10 #include "GrAppliedClip.h" 11 #include "GrContextPriv.h" 12 #include "GrDrawingManager.h" 13 #include "GrRenderTargetContextPriv.h" 14 #include "GrFixedClip.h" 15 #include "GrGpuResourcePriv.h" 16 #include "GrRenderTargetPriv.h" 17 #include "GrResourceProvider.h" 18 #include "GrStencilAttachment.h" 19 #include "GrSWMaskHelper.h" 20 #include "GrTextureProxy.h" 21 #include "effects/GrConvexPolyEffect.h" 22 #include "effects/GrRRectEffect.h" 23 #include "effects/GrTextureDomain.h" 24 #include "SkClipOpPriv.h" 25 26 typedef SkClipStack::Element Element; 27 typedef GrReducedClip::InitialState InitialState; 28 typedef GrReducedClip::ElementList ElementList; 29 30 static const int kMaxAnalyticElements = 4; 31 const char GrClipStackClip::kMaskTestTag[] = "clip_mask"; 32 33 bool GrClipStackClip::quickContains(const SkRect& rect) const { 34 if (!fStack || fStack->isWideOpen()) { 35 return true; 36 } 37 return fStack->quickContains(rect); 38 } 39 40 bool GrClipStackClip::quickContains(const SkRRect& rrect) const { 41 if (!fStack || fStack->isWideOpen()) { 42 return true; 43 } 44 return fStack->quickContains(rrect); 45 } 46 47 bool GrClipStackClip::isRRect(const SkRect& origRTBounds, SkRRect* rr, GrAA* aa) const { 48 if (!fStack) { 49 return false; 50 } 51 const SkRect* rtBounds = &origRTBounds; 52 bool isAA; 53 if (fStack->isRRect(*rtBounds, rr, &isAA)) { 54 *aa = GrBoolToAA(isAA); 55 return true; 56 } 57 return false; 58 } 59 60 void GrClipStackClip::getConservativeBounds(int width, int height, SkIRect* devResult, 61 bool* isIntersectionOfRects) const { 62 if (!fStack) { 63 devResult->setXYWH(0, 0, width, height); 64 if (isIntersectionOfRects) { 65 *isIntersectionOfRects = true; 66 } 67 return; 68 } 69 SkRect devBounds; 70 fStack->getConservativeBounds(0, 0, width, height, &devBounds, isIntersectionOfRects); 71 devBounds.roundOut(devResult); 72 } 73 74 //////////////////////////////////////////////////////////////////////////////// 75 // set up the draw state to enable the aa clipping mask. 76 static sk_sp<GrFragmentProcessor> create_fp_for_mask(sk_sp<GrTextureProxy> mask, 77 const SkIRect &devBound) { 78 SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height()); 79 return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(mask), domainTexels, 80 {devBound.fLeft, devBound.fTop}); 81 } 82 83 // Does the path in 'element' require SW rendering? If so, return true (and, 84 // optionally, set 'prOut' to NULL. If not, return false (and, optionally, set 85 // 'prOut' to the non-SW path renderer that will do the job). 86 bool GrClipStackClip::PathNeedsSWRenderer(GrContext* context, 87 bool hasUserStencilSettings, 88 const GrRenderTargetContext* renderTargetContext, 89 const SkMatrix& viewMatrix, 90 const Element* element, 91 GrPathRenderer** prOut, 92 bool needsStencil) { 93 if (Element::kRect_Type == element->getType()) { 94 // rects can always be drawn directly w/o using the software path 95 // TODO: skip rrects once we're drawing them directly. 96 if (prOut) { 97 *prOut = nullptr; 98 } 99 return false; 100 } else { 101 // We shouldn't get here with an empty clip element. 102 SkASSERT(Element::kEmpty_Type != element->getType()); 103 104 // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer 105 SkPath path; 106 element->asPath(&path); 107 if (path.isInverseFillType()) { 108 path.toggleInverseFillType(); 109 } 110 111 GrPathRendererChain::DrawType type = 112 needsStencil ? GrPathRendererChain::DrawType::kStencilAndColor 113 : GrPathRendererChain::DrawType::kColor; 114 115 GrShape shape(path, GrStyle::SimpleFill()); 116 GrPathRenderer::CanDrawPathArgs canDrawArgs; 117 canDrawArgs.fCaps = context->caps(); 118 canDrawArgs.fViewMatrix = &viewMatrix; 119 canDrawArgs.fShape = &shape; 120 canDrawArgs.fAAType = GrChooseAAType(GrBoolToAA(element->isAA()), 121 renderTargetContext->fsaaType(), 122 GrAllowMixedSamples::kYes, 123 *context->caps()); 124 canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings; 125 126 // the 'false' parameter disallows use of the SW path renderer 127 GrPathRenderer* pr = 128 context->contextPriv().drawingManager()->getPathRenderer(canDrawArgs, false, type); 129 if (prOut) { 130 *prOut = pr; 131 } 132 return SkToBool(!pr); 133 } 134 } 135 136 /* 137 * This method traverses the clip stack to see if the GrSoftwarePathRenderer 138 * will be used on any element. If so, it returns true to indicate that the 139 * entire clip should be rendered in SW and then uploaded en masse to the gpu. 140 */ 141 bool GrClipStackClip::UseSWOnlyPath(GrContext* context, 142 bool hasUserStencilSettings, 143 const GrRenderTargetContext* renderTargetContext, 144 const GrReducedClip& reducedClip) { 145 // TODO: generalize this function so that when 146 // a clip gets complex enough it can just be done in SW regardless 147 // of whether it would invoke the GrSoftwarePathRenderer. 148 149 // If we're avoiding stencils, always use SW: 150 if (context->caps()->avoidStencilBuffers()) 151 return true; 152 153 // Set the matrix so that rendered clip elements are transformed to mask space from clip 154 // space. 155 SkMatrix translate; 156 translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top())); 157 158 for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) { 159 const Element* element = iter.get(); 160 161 SkClipOp op = element->getOp(); 162 bool invert = element->isInverseFilled(); 163 bool needsStencil = invert || 164 kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op; 165 166 if (PathNeedsSWRenderer(context, hasUserStencilSettings, 167 renderTargetContext, translate, element, nullptr, needsStencil)) { 168 return true; 169 } 170 } 171 return false; 172 } 173 174 static bool get_analytic_clip_processor(const ElementList& elements, 175 bool abortIfAA, 176 const SkRect& drawDevBounds, 177 sk_sp<GrFragmentProcessor>* resultFP) { 178 SkASSERT(elements.count() <= kMaxAnalyticElements); 179 SkSTArray<kMaxAnalyticElements, sk_sp<GrFragmentProcessor>> fps; 180 ElementList::Iter iter(elements); 181 while (iter.get()) { 182 SkClipOp op = iter.get()->getOp(); 183 bool invert; 184 bool skip = false; 185 switch (op) { 186 case kReplace_SkClipOp: 187 SkASSERT(iter.get() == elements.head()); 188 // Fallthrough, handled same as intersect. 189 case kIntersect_SkClipOp: 190 invert = false; 191 if (iter.get()->contains(drawDevBounds)) { 192 skip = true; 193 } 194 break; 195 case kDifference_SkClipOp: 196 invert = true; 197 // We don't currently have a cheap test for whether a rect is fully outside an 198 // element's primitive, so don't attempt to set skip. 199 break; 200 default: 201 return false; 202 } 203 if (!skip) { 204 GrPrimitiveEdgeType edgeType; 205 if (iter.get()->isAA()) { 206 if (abortIfAA) { 207 return false; 208 } 209 edgeType = 210 invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType; 211 } else { 212 edgeType = 213 invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType; 214 } 215 216 switch (iter.get()->getType()) { 217 case SkClipStack::Element::kPath_Type: 218 fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getPath())); 219 break; 220 case SkClipStack::Element::kRRect_Type: { 221 fps.emplace_back(GrRRectEffect::Make(edgeType, iter.get()->getRRect())); 222 break; 223 } 224 case SkClipStack::Element::kRect_Type: { 225 fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getRect())); 226 break; 227 } 228 default: 229 break; 230 } 231 if (!fps.back()) { 232 return false; 233 } 234 } 235 iter.next(); 236 } 237 238 *resultFP = nullptr; 239 if (fps.count()) { 240 *resultFP = GrFragmentProcessor::RunInSeries(fps.begin(), fps.count()); 241 } 242 return true; 243 } 244 245 //////////////////////////////////////////////////////////////////////////////// 246 // sort out what kind of clip mask needs to be created: alpha, stencil, 247 // scissor, or entirely software 248 bool GrClipStackClip::apply(GrContext* context, GrRenderTargetContext* renderTargetContext, 249 bool useHWAA, bool hasUserStencilSettings, GrAppliedClip* out, 250 SkRect* bounds) const { 251 SkRect devBounds = SkRect::MakeIWH(renderTargetContext->width(), renderTargetContext->height()); 252 if (!devBounds.intersect(*bounds)) { 253 return false; 254 } 255 256 if (!fStack || fStack->isWideOpen()) { 257 return true; 258 } 259 260 const GrReducedClip reducedClip(*fStack, devBounds, 261 renderTargetContext->priv().maxWindowRectangles()); 262 263 if (reducedClip.hasIBounds() && !GrClip::IsInsideClip(reducedClip.ibounds(), devBounds)) { 264 out->addScissor(reducedClip.ibounds(), bounds); 265 } 266 267 if (!reducedClip.windowRectangles().empty()) { 268 out->addWindowRectangles(reducedClip.windowRectangles(), 269 GrWindowRectsState::Mode::kExclusive); 270 } 271 272 if (reducedClip.elements().isEmpty()) { 273 return InitialState::kAllIn == reducedClip.initialState(); 274 } 275 276 #ifdef SK_DEBUG 277 SkASSERT(reducedClip.hasIBounds()); 278 SkIRect rtIBounds = SkIRect::MakeWH(renderTargetContext->width(), 279 renderTargetContext->height()); 280 const SkIRect& clipIBounds = reducedClip.ibounds(); 281 SkASSERT(rtIBounds.contains(clipIBounds)); // Mask shouldn't be larger than the RT. 282 #endif 283 284 bool avoidStencilBuffers = context->caps()->avoidStencilBuffers(); 285 286 // An element count of 4 was chosen because of the common pattern in Blink of: 287 // isect RR 288 // diff RR 289 // isect convex_poly 290 // isect convex_poly 291 // when drawing rounded div borders. This could probably be tuned based on a 292 // configuration's relative costs of switching RTs to generate a mask vs 293 // longer shaders. 294 if (reducedClip.elements().count() <= kMaxAnalyticElements) { 295 // When there are multiple samples we want to do per-sample clipping, not compute a 296 // fractional pixel coverage. 297 bool disallowAnalyticAA = 298 GrFSAAType::kNone != renderTargetContext->fsaaType() && !avoidStencilBuffers; 299 if (disallowAnalyticAA && !renderTargetContext->numColorSamples()) { 300 // With a single color sample, any coverage info is lost from color once it hits the 301 // color buffer anyway, so we may as well use coverage AA if nothing else in the pipe 302 // is multisampled. 303 disallowAnalyticAA = useHWAA || hasUserStencilSettings; 304 } 305 sk_sp<GrFragmentProcessor> clipFP; 306 if ((reducedClip.requiresAA() || avoidStencilBuffers) && 307 get_analytic_clip_processor(reducedClip.elements(), disallowAnalyticAA, devBounds, 308 &clipFP)) { 309 out->addCoverageFP(std::move(clipFP)); 310 return true; 311 } 312 } 313 314 // If the stencil buffer is multisampled we can use it to do everything. 315 if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.requiresAA()) || 316 avoidStencilBuffers) { 317 sk_sp<GrTextureProxy> result; 318 if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) { 319 // The clip geometry is complex enough that it will be more efficient to create it 320 // entirely in software 321 result = this->createSoftwareClipMask(context, reducedClip); 322 } else { 323 result = this->createAlphaClipMask(context, reducedClip); 324 } 325 326 if (result) { 327 // The mask's top left coord should be pinned to the rounded-out top left corner of 328 // the clip's device space bounds. 329 out->addCoverageFP(create_fp_for_mask(std::move(result), reducedClip.ibounds())); 330 return true; 331 } 332 333 // If alpha or software clip mask creation fails, fall through to the stencil code paths, 334 // unless stencils are disallowed. 335 if (context->caps()->avoidStencilBuffers()) { 336 SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. Clip will be ignored.\n"); 337 return false; 338 } 339 } 340 341 GrRenderTarget* rt = renderTargetContext->accessRenderTarget(); 342 if (!rt) { 343 return true; 344 } 345 346 // use the stencil clip if we can't represent the clip as a rectangle. 347 if (!context->resourceProvider()->attachStencilAttachment(rt)) { 348 SkDebugf("WARNING: failed to attach stencil buffer for clip mask. Clip will be ignored.\n"); 349 return true; 350 } 351 352 // This relies on the property that a reduced sub-rect of the last clip will contain all the 353 // relevant window rectangles that were in the last clip. This subtle requirement will go away 354 // after clipping is overhauled. 355 if (renderTargetContext->priv().mustRenderClip(reducedClip.elementsGenID(), 356 reducedClip.ibounds())) { 357 reducedClip.drawStencilClipMask(context, renderTargetContext); 358 renderTargetContext->priv().setLastClip(reducedClip.elementsGenID(), reducedClip.ibounds()); 359 } 360 out->addStencilClip(reducedClip.elementsGenID()); 361 return true; 362 } 363 364 //////////////////////////////////////////////////////////////////////////////// 365 // Create a 8-bit clip mask in alpha 366 367 static void create_clip_mask_key(uint32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) { 368 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 369 GrUniqueKey::Builder builder(key, kDomain, 3, GrClipStackClip::kMaskTestTag); 370 builder[0] = clipGenID; 371 // SkToS16 because image filters outset layers to a size indicated by the filter, which can 372 // sometimes result in negative coordinates from device space. 373 builder[1] = SkToS16(bounds.fLeft) | (SkToS16(bounds.fRight) << 16); 374 builder[2] = SkToS16(bounds.fTop) | (SkToS16(bounds.fBottom) << 16); 375 } 376 377 static void add_invalidate_on_pop_message(const SkClipStack& stack, uint32_t clipGenID, 378 const GrUniqueKey& clipMaskKey) { 379 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); 380 while (const Element* element = iter.prev()) { 381 if (element->getGenID() == clipGenID) { 382 std::unique_ptr<GrUniqueKeyInvalidatedMessage> msg( 383 new GrUniqueKeyInvalidatedMessage(clipMaskKey)); 384 element->addResourceInvalidationMessage(std::move(msg)); 385 return; 386 } 387 } 388 SkDEBUGFAIL("Gen ID was not found in stack."); 389 } 390 391 sk_sp<GrTextureProxy> GrClipStackClip::createAlphaClipMask(GrContext* context, 392 const GrReducedClip& reducedClip) const { 393 GrResourceProvider* resourceProvider = context->resourceProvider(); 394 GrUniqueKey key; 395 create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key); 396 397 sk_sp<GrTextureProxy> proxy(resourceProvider->findProxyByUniqueKey(key)); 398 if (proxy) { 399 return proxy; 400 } 401 402 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContextWithFallback( 403 SkBackingFit::kApprox, 404 reducedClip.width(), 405 reducedClip.height(), 406 kAlpha_8_GrPixelConfig, 407 nullptr)); 408 if (!rtc) { 409 return nullptr; 410 } 411 412 if (!reducedClip.drawAlphaClipMask(rtc.get())) { 413 return nullptr; 414 } 415 416 sk_sp<GrTextureProxy> result(rtc->asTextureProxyRef()); 417 if (!result) { 418 return nullptr; 419 } 420 421 resourceProvider->assignUniqueKeyToProxy(key, result.get()); 422 // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching 423 add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key); 424 425 return result; 426 } 427 428 sk_sp<GrTextureProxy> GrClipStackClip::createSoftwareClipMask( 429 GrContext* context, 430 const GrReducedClip& reducedClip) const { 431 GrUniqueKey key; 432 create_clip_mask_key(reducedClip.elementsGenID(), reducedClip.ibounds(), &key); 433 434 sk_sp<GrTextureProxy> proxy(context->resourceProvider()->findProxyByUniqueKey(key)); 435 if (proxy) { 436 return proxy; 437 } 438 439 // The mask texture may be larger than necessary. We round out the clip bounds and pin the top 440 // left corner of the resulting rect to the top left of the texture. 441 SkIRect maskSpaceIBounds = SkIRect::MakeWH(reducedClip.width(), reducedClip.height()); 442 443 GrSWMaskHelper helper; 444 445 // Set the matrix so that rendered clip elements are transformed to mask space from clip 446 // space. 447 SkMatrix translate; 448 translate.setTranslate(SkIntToScalar(-reducedClip.left()), SkIntToScalar(-reducedClip.top())); 449 450 if (!helper.init(maskSpaceIBounds, &translate)) { 451 return nullptr; 452 } 453 helper.clear(InitialState::kAllIn == reducedClip.initialState() ? 0xFF : 0x00); 454 455 for (ElementList::Iter iter(reducedClip.elements()); iter.get(); iter.next()) { 456 const Element* element = iter.get(); 457 SkClipOp op = element->getOp(); 458 GrAA aa = GrBoolToAA(element->isAA()); 459 460 if (kIntersect_SkClipOp == op || kReverseDifference_SkClipOp == op) { 461 // Intersect and reverse difference require modifying pixels outside of the geometry 462 // that is being "drawn". In both cases we erase all the pixels outside of the geometry 463 // but leave the pixels inside the geometry alone. For reverse difference we invert all 464 // the pixels before clearing the ones outside the geometry. 465 if (kReverseDifference_SkClipOp == op) { 466 SkRect temp = SkRect::Make(reducedClip.ibounds()); 467 // invert the entire scene 468 helper.drawRect(temp, SkRegion::kXOR_Op, GrAA::kNo, 0xFF); 469 } 470 SkPath clipPath; 471 element->asPath(&clipPath); 472 clipPath.toggleInverseFillType(); 473 GrShape shape(clipPath, GrStyle::SimpleFill()); 474 helper.drawShape(shape, SkRegion::kReplace_Op, aa, 0x00); 475 continue; 476 } 477 478 // The other ops (union, xor, diff) only affect pixels inside 479 // the geometry so they can just be drawn normally 480 if (Element::kRect_Type == element->getType()) { 481 helper.drawRect(element->getRect(), (SkRegion::Op)op, aa, 0xFF); 482 } else { 483 SkPath path; 484 element->asPath(&path); 485 GrShape shape(path, GrStyle::SimpleFill()); 486 helper.drawShape(shape, (SkRegion::Op)op, aa, 0xFF); 487 } 488 } 489 490 sk_sp<GrTextureProxy> result(helper.toTextureProxy(context, SkBackingFit::kApprox)); 491 492 context->resourceProvider()->assignUniqueKeyToProxy(key, result.get()); 493 // MDB TODO (caching): this has to play nice with the GrSurfaceProxy's caching 494 add_invalidate_on_pop_message(*fStack, reducedClip.elementsGenID(), key); 495 return result; 496 } 497