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