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