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