1 /* 2 * Copyright 2012 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 "GrClipMaskManager.h" 9 #include "GrCaps.h" 10 #include "GrDrawingManager.h" 11 #include "GrDrawContext.h" 12 #include "GrDrawTarget.h" 13 #include "GrGpuResourcePriv.h" 14 #include "GrPaint.h" 15 #include "GrPathRenderer.h" 16 #include "GrRenderTarget.h" 17 #include "GrRenderTargetPriv.h" 18 #include "GrResourceProvider.h" 19 #include "GrStencilAttachment.h" 20 #include "GrSWMaskHelper.h" 21 #include "SkRasterClip.h" 22 #include "SkTLazy.h" 23 #include "batches/GrRectBatchFactory.h" 24 #include "effects/GrConvexPolyEffect.h" 25 #include "effects/GrPorterDuffXferProcessor.h" 26 #include "effects/GrRRectEffect.h" 27 #include "effects/GrTextureDomain.h" 28 29 typedef SkClipStack::Element Element; 30 31 //////////////////////////////////////////////////////////////////////////////// 32 // set up the draw state to enable the aa clipping mask. Besides setting up the 33 // stage matrix this also alters the vertex layout 34 static const GrFragmentProcessor* create_fp_for_mask(GrTexture* result, const SkIRect &devBound) { 35 SkMatrix mat; 36 // We use device coords to compute the texture coordinates. We set our matrix to be a 37 // translation to the devBound, and then a scaling matrix to normalized coords. 38 mat.setIDiv(result->width(), result->height()); 39 mat.preTranslate(SkIntToScalar(-devBound.fLeft), 40 SkIntToScalar(-devBound.fTop)); 41 42 SkIRect domainTexels = SkIRect::MakeWH(devBound.width(), devBound.height()); 43 return GrTextureDomainEffect::Create(result, 44 mat, 45 GrTextureDomain::MakeTexelDomain(result, domainTexels), 46 GrTextureDomain::kDecal_Mode, 47 GrTextureParams::kNone_FilterMode, 48 kDevice_GrCoordSet); 49 } 50 51 static void draw_non_aa_rect(GrDrawTarget* drawTarget, 52 const GrPipelineBuilder& pipelineBuilder, 53 GrColor color, 54 const SkMatrix& viewMatrix, 55 const SkRect& rect) { 56 SkAutoTUnref<GrDrawBatch> batch(GrRectBatchFactory::CreateNonAAFill(color, viewMatrix, rect, 57 nullptr, nullptr)); 58 drawTarget->drawBatch(pipelineBuilder, batch); 59 } 60 61 // Does the path in 'element' require SW rendering? If so, return true (and, 62 // optionally, set 'prOut' to NULL. If not, return false (and, optionally, set 63 // 'prOut' to the non-SW path renderer that will do the job). 64 bool GrClipMaskManager::PathNeedsSWRenderer(GrContext* context, 65 bool isStencilDisabled, 66 const GrRenderTarget* rt, 67 const SkMatrix& viewMatrix, 68 const Element* element, 69 GrPathRenderer** prOut, 70 bool needsStencil) { 71 if (Element::kRect_Type == element->getType()) { 72 // rects can always be drawn directly w/o using the software path 73 // TODO: skip rrects once we're drawing them directly. 74 if (prOut) { 75 *prOut = nullptr; 76 } 77 return false; 78 } else { 79 // We shouldn't get here with an empty clip element. 80 SkASSERT(Element::kEmpty_Type != element->getType()); 81 82 // the gpu alpha mask will draw the inverse paths as non-inverse to a temp buffer 83 SkPath path; 84 element->asPath(&path); 85 if (path.isInverseFillType()) { 86 path.toggleInverseFillType(); 87 } 88 GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); 89 90 GrPathRendererChain::DrawType type; 91 92 if (needsStencil) { 93 type = element->isAA() 94 ? GrPathRendererChain::kStencilAndColorAntiAlias_DrawType 95 : GrPathRendererChain::kStencilAndColor_DrawType; 96 } else { 97 type = element->isAA() 98 ? GrPathRendererChain::kColorAntiAlias_DrawType 99 : GrPathRendererChain::kColor_DrawType; 100 } 101 102 GrPathRenderer::CanDrawPathArgs canDrawArgs; 103 canDrawArgs.fShaderCaps = context->caps()->shaderCaps(); 104 canDrawArgs.fViewMatrix = &viewMatrix; 105 canDrawArgs.fPath = &path; 106 canDrawArgs.fStroke = &stroke; 107 canDrawArgs.fAntiAlias = element->isAA(); 108 canDrawArgs.fIsStencilDisabled = isStencilDisabled; 109 canDrawArgs.fIsStencilBufferMSAA = rt->isStencilBufferMultisampled(); 110 111 // the 'false' parameter disallows use of the SW path renderer 112 GrPathRenderer* pr = context->drawingManager()->getPathRenderer(canDrawArgs, false, type); 113 if (prOut) { 114 *prOut = pr; 115 } 116 return SkToBool(!pr); 117 } 118 } 119 120 // Determines whether it is possible to draw the element to both the stencil buffer and the 121 // alpha mask simultaneously. If so and the element is a path a compatible path renderer is 122 // also returned. 123 GrPathRenderer* GrClipMaskManager::GetPathRenderer(GrContext* context, 124 GrTexture* texture, 125 const SkMatrix& viewMatrix, 126 const SkClipStack::Element* element) { 127 GrPathRenderer* pr; 128 static const bool kNeedsStencil = true; 129 static const bool kStencilIsDisabled = true; 130 PathNeedsSWRenderer(context, 131 kStencilIsDisabled, 132 texture->asRenderTarget(), 133 viewMatrix, 134 element, 135 &pr, 136 kNeedsStencil); 137 return pr; 138 } 139 140 GrClipMaskManager::GrClipMaskManager(GrDrawTarget* drawTarget, bool debugClipBatchToBounds) 141 : fDrawTarget(drawTarget) 142 , fClipMode(kIgnoreClip_StencilClipMode) 143 , fDebugClipBatchToBounds(debugClipBatchToBounds) { 144 } 145 146 GrContext* GrClipMaskManager::getContext() { 147 return fDrawTarget->cmmAccess().context(); 148 } 149 150 const GrCaps* GrClipMaskManager::caps() const { 151 return fDrawTarget->caps(); 152 } 153 154 GrResourceProvider* GrClipMaskManager::resourceProvider() { 155 return fDrawTarget->cmmAccess().resourceProvider(); 156 } 157 /* 158 * This method traverses the clip stack to see if the GrSoftwarePathRenderer 159 * will be used on any element. If so, it returns true to indicate that the 160 * entire clip should be rendered in SW and then uploaded en masse to the gpu. 161 */ 162 bool GrClipMaskManager::useSWOnlyPath(const GrPipelineBuilder& pipelineBuilder, 163 const GrRenderTarget* rt, 164 const SkVector& clipToMaskOffset, 165 const GrReducedClip::ElementList& elements) { 166 // TODO: generalize this function so that when 167 // a clip gets complex enough it can just be done in SW regardless 168 // of whether it would invoke the GrSoftwarePathRenderer. 169 170 // Set the matrix so that rendered clip elements are transformed to mask space from clip 171 // space. 172 const SkMatrix translate = SkMatrix::MakeTrans(clipToMaskOffset.fX, clipToMaskOffset.fY); 173 174 for (GrReducedClip::ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) { 175 const Element* element = iter.get(); 176 177 SkRegion::Op op = element->getOp(); 178 bool invert = element->isInverseFilled(); 179 bool needsStencil = invert || 180 SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op; 181 182 if (PathNeedsSWRenderer(this->getContext(), pipelineBuilder.getStencil().isDisabled(), 183 rt, translate, element, nullptr, needsStencil)) { 184 return true; 185 } 186 } 187 return false; 188 } 189 190 bool GrClipMaskManager::getAnalyticClipProcessor(const GrReducedClip::ElementList& elements, 191 bool abortIfAA, 192 SkVector& clipToRTOffset, 193 const SkRect* drawBounds, 194 const GrFragmentProcessor** resultFP) { 195 SkRect boundsInClipSpace; 196 if (drawBounds) { 197 boundsInClipSpace = *drawBounds; 198 boundsInClipSpace.offset(-clipToRTOffset.fX, -clipToRTOffset.fY); 199 } 200 SkASSERT(elements.count() <= kMaxAnalyticElements); 201 const GrFragmentProcessor* fps[kMaxAnalyticElements]; 202 for (int i = 0; i < kMaxAnalyticElements; ++i) { 203 fps[i] = nullptr; 204 } 205 int fpCnt = 0; 206 GrReducedClip::ElementList::Iter iter(elements); 207 bool failed = false; 208 while (iter.get()) { 209 SkRegion::Op op = iter.get()->getOp(); 210 bool invert; 211 bool skip = false; 212 switch (op) { 213 case SkRegion::kReplace_Op: 214 SkASSERT(iter.get() == elements.head()); 215 // Fallthrough, handled same as intersect. 216 case SkRegion::kIntersect_Op: 217 invert = false; 218 if (drawBounds && iter.get()->contains(boundsInClipSpace)) { 219 skip = true; 220 } 221 break; 222 case SkRegion::kDifference_Op: 223 invert = true; 224 // We don't currently have a cheap test for whether a rect is fully outside an 225 // element's primitive, so don't attempt to set skip. 226 break; 227 default: 228 failed = true; 229 break; 230 } 231 if (failed) { 232 break; 233 } 234 if (!skip) { 235 GrPrimitiveEdgeType edgeType; 236 if (iter.get()->isAA()) { 237 if (abortIfAA) { 238 failed = true; 239 break; 240 } 241 edgeType = 242 invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType; 243 } else { 244 edgeType = 245 invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType; 246 } 247 248 switch (iter.get()->getType()) { 249 case SkClipStack::Element::kPath_Type: 250 fps[fpCnt] = GrConvexPolyEffect::Create(edgeType, iter.get()->getPath(), 251 &clipToRTOffset); 252 break; 253 case SkClipStack::Element::kRRect_Type: { 254 SkRRect rrect = iter.get()->getRRect(); 255 rrect.offset(clipToRTOffset.fX, clipToRTOffset.fY); 256 fps[fpCnt] = GrRRectEffect::Create(edgeType, rrect); 257 break; 258 } 259 case SkClipStack::Element::kRect_Type: { 260 SkRect rect = iter.get()->getRect(); 261 rect.offset(clipToRTOffset.fX, clipToRTOffset.fY); 262 fps[fpCnt] = GrConvexPolyEffect::Create(edgeType, rect); 263 break; 264 } 265 default: 266 break; 267 } 268 if (!fps[fpCnt]) { 269 failed = true; 270 break; 271 } 272 fpCnt++; 273 } 274 iter.next(); 275 } 276 277 *resultFP = nullptr; 278 if (!failed && fpCnt) { 279 *resultFP = GrFragmentProcessor::RunInSeries(fps, fpCnt); 280 } 281 for (int i = 0; i < fpCnt; ++i) { 282 fps[i]->unref(); 283 } 284 return !failed; 285 } 286 287 static void add_rect_to_clip(const GrClip& clip, const SkRect& devRect, GrClip* out) { 288 switch (clip.clipType()) { 289 case GrClip::kClipStack_ClipType: { 290 SkClipStack* stack = new SkClipStack; 291 *stack = *clip.clipStack(); 292 // The stack is actually in clip space not device space. 293 SkRect clipRect = devRect; 294 SkPoint origin = { SkIntToScalar(clip.origin().fX), SkIntToScalar(clip.origin().fY) }; 295 clipRect.offset(origin); 296 SkIRect iclipRect; 297 clipRect.roundOut(&iclipRect); 298 clipRect = SkRect::Make(iclipRect); 299 stack->clipDevRect(clipRect, SkRegion::kIntersect_Op, false); 300 out->setClipStack(stack, &clip.origin()); 301 break; 302 } 303 case GrClip::kWideOpen_ClipType: 304 *out = GrClip(devRect); 305 break; 306 case GrClip::kIRect_ClipType: { 307 SkIRect intersect; 308 devRect.roundOut(&intersect); 309 if (intersect.intersect(clip.irect())) { 310 *out = GrClip(intersect); 311 } else { 312 *out = clip; 313 } 314 break; 315 } 316 } 317 } 318 319 //////////////////////////////////////////////////////////////////////////////// 320 // sort out what kind of clip mask needs to be created: alpha, stencil, 321 // scissor, or entirely software 322 bool GrClipMaskManager::setupClipping(const GrPipelineBuilder& pipelineBuilder, 323 GrPipelineBuilder::AutoRestoreStencil* ars, 324 const SkRect* devBounds, 325 GrAppliedClip* out) { 326 if (kRespectClip_StencilClipMode == fClipMode) { 327 fClipMode = kIgnoreClip_StencilClipMode; 328 } 329 330 GrReducedClip::ElementList elements; 331 int32_t genID = 0; 332 GrReducedClip::InitialState initialState = GrReducedClip::kAllIn_InitialState; 333 SkIRect clipSpaceIBounds; 334 bool requiresAA = false; 335 GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); 336 337 // GrDrawTarget should have filtered this for us 338 SkASSERT(rt); 339 340 SkIRect clipSpaceRTIBounds = SkIRect::MakeWH(rt->width(), rt->height()); 341 GrClip devBoundsClip; 342 bool doDevBoundsClip = fDebugClipBatchToBounds && devBounds; 343 if (doDevBoundsClip) { 344 add_rect_to_clip(pipelineBuilder.clip(), *devBounds, &devBoundsClip); 345 } 346 const GrClip& clip = doDevBoundsClip ? devBoundsClip : pipelineBuilder.clip(); 347 348 if (clip.isWideOpen(clipSpaceRTIBounds)) { 349 this->setPipelineBuilderStencil(pipelineBuilder, ars); 350 return true; 351 } 352 353 // The clip mask manager always draws with a single IRect so we special case that logic here 354 // Image filters just use a rect, so we also special case that logic 355 switch (clip.clipType()) { 356 case GrClip::kWideOpen_ClipType: 357 SkFAIL("Should have caught this with clip.isWideOpen()"); 358 return true; 359 case GrClip::kIRect_ClipType: { 360 SkIRect scissor = clip.irect(); 361 if (scissor.intersect(clipSpaceRTIBounds)) { 362 out->fScissorState.set(scissor); 363 this->setPipelineBuilderStencil(pipelineBuilder, ars); 364 return true; 365 } 366 return false; 367 } 368 case GrClip::kClipStack_ClipType: { 369 clipSpaceRTIBounds.offset(clip.origin()); 370 SkIRect clipSpaceReduceQueryBounds; 371 #define DISABLE_DEV_BOUNDS_FOR_CLIP_REDUCTION 1 372 if (devBounds && !DISABLE_DEV_BOUNDS_FOR_CLIP_REDUCTION) { 373 SkIRect devIBounds = devBounds->roundOut(); 374 devIBounds.offset(clip.origin()); 375 if (!clipSpaceReduceQueryBounds.intersect(clipSpaceRTIBounds, devIBounds)) { 376 return false; 377 } 378 } else { 379 clipSpaceReduceQueryBounds = clipSpaceRTIBounds; 380 } 381 GrReducedClip::ReduceClipStack(*clip.clipStack(), 382 clipSpaceReduceQueryBounds, 383 &elements, 384 &genID, 385 &initialState, 386 &clipSpaceIBounds, 387 &requiresAA); 388 if (elements.isEmpty()) { 389 if (GrReducedClip::kAllIn_InitialState == initialState) { 390 if (clipSpaceIBounds == clipSpaceRTIBounds) { 391 this->setPipelineBuilderStencil(pipelineBuilder, ars); 392 return true; 393 } 394 } else { 395 return false; 396 } 397 } 398 } break; 399 } 400 401 // An element count of 4 was chosen because of the common pattern in Blink of: 402 // isect RR 403 // diff RR 404 // isect convex_poly 405 // isect convex_poly 406 // when drawing rounded div borders. This could probably be tuned based on a 407 // configuration's relative costs of switching RTs to generate a mask vs 408 // longer shaders. 409 if (elements.count() <= kMaxAnalyticElements) { 410 SkVector clipToRTOffset = { SkIntToScalar(-clip.origin().fX), 411 SkIntToScalar(-clip.origin().fY) }; 412 // When there are multiple samples we want to do per-sample clipping, not compute a 413 // fractional pixel coverage. 414 bool disallowAnalyticAA = rt->isUnifiedMultisampled() || pipelineBuilder.hasMixedSamples(); 415 const GrFragmentProcessor* clipFP = nullptr; 416 if (elements.isEmpty() || 417 (requiresAA && 418 this->getAnalyticClipProcessor(elements, disallowAnalyticAA, clipToRTOffset, devBounds, 419 &clipFP))) { 420 SkIRect scissorSpaceIBounds(clipSpaceIBounds); 421 scissorSpaceIBounds.offset(-clip.origin()); 422 if (nullptr == devBounds || 423 !SkRect::Make(scissorSpaceIBounds).contains(*devBounds)) { 424 out->fScissorState.set(scissorSpaceIBounds); 425 } 426 this->setPipelineBuilderStencil(pipelineBuilder, ars); 427 out->fClipCoverageFP.reset(clipFP); 428 return true; 429 } 430 } 431 432 // If the stencil buffer is multisampled we can use it to do everything. 433 if (!rt->isStencilBufferMultisampled() && requiresAA) { 434 SkAutoTUnref<GrTexture> result; 435 436 // The top-left of the mask corresponds to the top-left corner of the bounds. 437 SkVector clipToMaskOffset = { 438 SkIntToScalar(-clipSpaceIBounds.fLeft), 439 SkIntToScalar(-clipSpaceIBounds.fTop) 440 }; 441 442 if (this->useSWOnlyPath(pipelineBuilder, rt, clipToMaskOffset, elements)) { 443 // The clip geometry is complex enough that it will be more efficient to create it 444 // entirely in software 445 result.reset(this->createSoftwareClipMask(genID, 446 initialState, 447 elements, 448 clipToMaskOffset, 449 clipSpaceIBounds)); 450 } else { 451 result.reset(this->createAlphaClipMask(genID, 452 initialState, 453 elements, 454 clipToMaskOffset, 455 clipSpaceIBounds)); 456 // If createAlphaClipMask fails it means useSWOnlyPath has a bug 457 SkASSERT(result); 458 } 459 460 if (result) { 461 // The mask's top left coord should be pinned to the rounded-out top left corner of 462 // clipSpace bounds. We determine the mask's position WRT to the render target here. 463 SkIRect rtSpaceMaskBounds = clipSpaceIBounds; 464 rtSpaceMaskBounds.offset(-clip.origin()); 465 out->fClipCoverageFP.reset(create_fp_for_mask(result, rtSpaceMaskBounds)); 466 this->setPipelineBuilderStencil(pipelineBuilder, ars); 467 return true; 468 } 469 // if alpha clip mask creation fails fall through to the non-AA code paths 470 } 471 472 // use the stencil clip if we can't represent the clip as a rectangle. 473 SkIPoint clipSpaceToStencilSpaceOffset = -clip.origin(); 474 this->createStencilClipMask(rt, 475 genID, 476 initialState, 477 elements, 478 clipSpaceIBounds, 479 clipSpaceToStencilSpaceOffset); 480 481 // This must occur after createStencilClipMask. That function may change the scissor. Also, it 482 // only guarantees that the stencil mask is correct within the bounds it was passed, so we must 483 // use both stencil and scissor test to the bounds for the final draw. 484 SkIRect scissorSpaceIBounds(clipSpaceIBounds); 485 scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset); 486 out->fScissorState.set(scissorSpaceIBounds); 487 this->setPipelineBuilderStencil(pipelineBuilder, ars); 488 return true; 489 } 490 491 namespace { 492 //////////////////////////////////////////////////////////////////////////////// 493 // Set a coverage drawing XPF on the pipelineBuilder for the given op and invertCoverage mode 494 void set_coverage_drawing_xpf(SkRegion::Op op, bool invertCoverage, 495 GrPipelineBuilder* pipelineBuilder) { 496 SkASSERT(op <= SkRegion::kLastOp); 497 pipelineBuilder->setCoverageSetOpXPFactory(op, invertCoverage); 498 } 499 } 500 501 //////////////////////////////////////////////////////////////////////////////// 502 bool GrClipMaskManager::drawElement(GrPipelineBuilder* pipelineBuilder, 503 const SkMatrix& viewMatrix, 504 GrTexture* target, 505 const SkClipStack::Element* element, 506 GrPathRenderer* pr) { 507 508 GrRenderTarget* rt = target->asRenderTarget(); 509 pipelineBuilder->setRenderTarget(rt); 510 511 // The color we use to draw does not matter since we will always be using a GrCoverageSetOpXP 512 // which ignores color. 513 GrColor color = GrColor_WHITE; 514 515 // TODO: Draw rrects directly here. 516 switch (element->getType()) { 517 case Element::kEmpty_Type: 518 SkDEBUGFAIL("Should never get here with an empty element."); 519 break; 520 case Element::kRect_Type: { 521 // TODO: Do rects directly to the accumulator using a aa-rect GrProcessor that covers 522 // the entire mask bounds and writes 0 outside the rect. 523 if (element->isAA()) { 524 SkRect devRect = element->getRect(); 525 viewMatrix.mapRect(&devRect); 526 527 SkAutoTUnref<GrDrawBatch> batch( 528 GrRectBatchFactory::CreateAAFill(color, viewMatrix, element->getRect(), 529 devRect)); 530 531 fDrawTarget->drawBatch(*pipelineBuilder, batch); 532 } else { 533 draw_non_aa_rect(fDrawTarget, *pipelineBuilder, color, viewMatrix, 534 element->getRect()); 535 } 536 return true; 537 } 538 default: { 539 SkPath path; 540 element->asPath(&path); 541 if (path.isInverseFillType()) { 542 path.toggleInverseFillType(); 543 } 544 GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); 545 if (nullptr == pr) { 546 GrPathRendererChain::DrawType type; 547 type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType : 548 GrPathRendererChain::kColor_DrawType; 549 550 GrPathRenderer::CanDrawPathArgs canDrawArgs; 551 canDrawArgs.fShaderCaps = this->getContext()->caps()->shaderCaps(); 552 canDrawArgs.fViewMatrix = &viewMatrix; 553 canDrawArgs.fPath = &path; 554 canDrawArgs.fStroke = &stroke; 555 canDrawArgs.fAntiAlias = element->isAA();; 556 canDrawArgs.fIsStencilDisabled = pipelineBuilder->getStencil().isDisabled(); 557 canDrawArgs.fIsStencilBufferMSAA = rt->isStencilBufferMultisampled(); 558 559 pr = this->getContext()->drawingManager()->getPathRenderer(canDrawArgs, false, type); 560 } 561 if (nullptr == pr) { 562 return false; 563 } 564 GrPathRenderer::DrawPathArgs args; 565 args.fTarget = fDrawTarget; 566 args.fResourceProvider = this->getContext()->resourceProvider(); 567 args.fPipelineBuilder = pipelineBuilder; 568 args.fColor = color; 569 args.fViewMatrix = &viewMatrix; 570 args.fPath = &path; 571 args.fStroke = &stroke; 572 args.fAntiAlias = element->isAA(); 573 pr->drawPath(args); 574 break; 575 } 576 } 577 return true; 578 } 579 580 //////////////////////////////////////////////////////////////////////////////// 581 // Create a 8-bit clip mask in alpha 582 583 static void GetClipMaskKey(int32_t clipGenID, const SkIRect& bounds, GrUniqueKey* key) { 584 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); 585 GrUniqueKey::Builder builder(key, kDomain, 3); 586 builder[0] = clipGenID; 587 builder[1] = SkToU16(bounds.fLeft) | (SkToU16(bounds.fRight) << 16); 588 builder[2] = SkToU16(bounds.fTop) | (SkToU16(bounds.fBottom) << 16); 589 } 590 591 GrTexture* GrClipMaskManager::createCachedMask(int width, int height, const GrUniqueKey& key, 592 bool renderTarget) { 593 GrSurfaceDesc desc; 594 desc.fWidth = width; 595 desc.fHeight = height; 596 desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags; 597 if (!renderTarget || this->caps()->isConfigRenderable(kAlpha_8_GrPixelConfig, false)) { 598 desc.fConfig = kAlpha_8_GrPixelConfig; 599 } else { 600 desc.fConfig = kRGBA_8888_GrPixelConfig; 601 } 602 603 GrTexture* texture = this->resourceProvider()->createApproxTexture(desc, 0); 604 if (!texture) { 605 return nullptr; 606 } 607 texture->resourcePriv().setUniqueKey(key); 608 return texture; 609 } 610 611 GrTexture* GrClipMaskManager::createAlphaClipMask(int32_t elementsGenID, 612 GrReducedClip::InitialState initialState, 613 const GrReducedClip::ElementList& elements, 614 const SkVector& clipToMaskOffset, 615 const SkIRect& clipSpaceIBounds) { 616 GrResourceProvider* resourceProvider = this->resourceProvider(); 617 GrUniqueKey key; 618 GetClipMaskKey(elementsGenID, clipSpaceIBounds, &key); 619 if (GrTexture* texture = resourceProvider->findAndRefTextureByUniqueKey(key)) { 620 return texture; 621 } 622 623 // There's no texture in the cache. Let's try to allocate it then. 624 SkAutoTUnref<GrTexture> texture(this->createCachedMask( 625 clipSpaceIBounds.width(), clipSpaceIBounds.height(), key, true)); 626 if (!texture) { 627 return nullptr; 628 } 629 630 // Set the matrix so that rendered clip elements are transformed to mask space from clip 631 // space. 632 const SkMatrix translate = SkMatrix::MakeTrans(clipToMaskOffset.fX, clipToMaskOffset.fY); 633 634 // The texture may be larger than necessary, this rect represents the part of the texture 635 // we populate with a rasterization of the clip. 636 SkIRect maskSpaceIBounds = SkIRect::MakeWH(clipSpaceIBounds.width(), clipSpaceIBounds.height()); 637 638 // The scratch texture that we are drawing into can be substantially larger than the mask. Only 639 // clear the part that we care about. 640 fDrawTarget->clear(&maskSpaceIBounds, 641 GrReducedClip::kAllIn_InitialState == initialState ? 0xffffffff : 0x00000000, 642 true, 643 texture->asRenderTarget()); 644 645 // When we use the stencil in the below loop it is important to have this clip installed. 646 // The second pass that zeros the stencil buffer renders the rect maskSpaceIBounds so the first 647 // pass must not set values outside of this bounds or stencil values outside the rect won't be 648 // cleared. 649 const GrClip clip(maskSpaceIBounds); 650 651 // walk through each clip element and perform its set op 652 for (GrReducedClip::ElementList::Iter iter = elements.headIter(); iter.get(); iter.next()) { 653 const Element* element = iter.get(); 654 SkRegion::Op op = element->getOp(); 655 bool invert = element->isInverseFilled(); 656 if (invert || SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { 657 658 GrPathRenderer* pr = GetPathRenderer(this->getContext(), 659 texture, translate, element); 660 if (Element::kRect_Type != element->getType() && !pr) { 661 // useSWOnlyPath should now filter out all cases where gpu-side mask merging would 662 // be performed (i.e., pr would be NULL for a non-rect path). See https://bug.skia.org/4519 663 // for rationale and details. 664 SkASSERT(0); 665 continue; 666 } 667 668 { 669 GrPipelineBuilder pipelineBuilder; 670 671 pipelineBuilder.setClip(clip); 672 pipelineBuilder.setRenderTarget(texture->asRenderTarget()); 673 SkASSERT(pipelineBuilder.getStencil().isDisabled()); 674 675 // draw directly into the result with the stencil set to make the pixels affected 676 // by the clip shape be non-zero. 677 GR_STATIC_CONST_SAME_STENCIL(kStencilInElement, 678 kReplace_StencilOp, 679 kReplace_StencilOp, 680 kAlways_StencilFunc, 681 0xffff, 682 0xffff, 683 0xffff); 684 pipelineBuilder.setStencil(kStencilInElement); 685 set_coverage_drawing_xpf(op, invert, &pipelineBuilder); 686 687 if (!this->drawElement(&pipelineBuilder, translate, texture, element, pr)) { 688 texture->resourcePriv().removeUniqueKey(); 689 return nullptr; 690 } 691 } 692 693 { 694 GrPipelineBuilder backgroundPipelineBuilder; 695 backgroundPipelineBuilder.setRenderTarget(texture->asRenderTarget()); 696 697 set_coverage_drawing_xpf(op, !invert, &backgroundPipelineBuilder); 698 // Draw to the exterior pixels (those with a zero stencil value). 699 GR_STATIC_CONST_SAME_STENCIL(kDrawOutsideElement, 700 kZero_StencilOp, 701 kZero_StencilOp, 702 kEqual_StencilFunc, 703 0xffff, 704 0x0000, 705 0xffff); 706 backgroundPipelineBuilder.setStencil(kDrawOutsideElement); 707 708 // The color passed in here does not matter since the coverageSetOpXP won't read it. 709 draw_non_aa_rect(fDrawTarget, backgroundPipelineBuilder, GrColor_WHITE, translate, 710 SkRect::Make(clipSpaceIBounds)); 711 } 712 } else { 713 GrPipelineBuilder pipelineBuilder; 714 715 // all the remaining ops can just be directly draw into the accumulation buffer 716 set_coverage_drawing_xpf(op, false, &pipelineBuilder); 717 // The color passed in here does not matter since the coverageSetOpXP won't read it. 718 this->drawElement(&pipelineBuilder, translate, texture, element); 719 } 720 } 721 722 return texture.detach(); 723 } 724 725 //////////////////////////////////////////////////////////////////////////////// 726 // Create a 1-bit clip mask in the stencil buffer. 'devClipBounds' are in device 727 // (as opposed to canvas) coordinates 728 bool GrClipMaskManager::createStencilClipMask(GrRenderTarget* rt, 729 int32_t elementsGenID, 730 GrReducedClip::InitialState initialState, 731 const GrReducedClip::ElementList& elements, 732 const SkIRect& clipSpaceIBounds, 733 const SkIPoint& clipSpaceToStencilOffset) { 734 SkASSERT(rt); 735 736 GrStencilAttachment* stencilAttachment = this->resourceProvider()->attachStencilAttachment(rt); 737 if (nullptr == stencilAttachment) { 738 return false; 739 } 740 741 if (stencilAttachment->mustRenderClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset)) { 742 stencilAttachment->setLastClip(elementsGenID, clipSpaceIBounds, clipSpaceToStencilOffset); 743 // Set the matrix so that rendered clip elements are transformed from clip to stencil space. 744 SkVector translate = { 745 SkIntToScalar(clipSpaceToStencilOffset.fX), 746 SkIntToScalar(clipSpaceToStencilOffset.fY) 747 }; 748 SkMatrix viewMatrix; 749 viewMatrix.setTranslate(translate); 750 751 // We set the current clip to the bounds so that our recursive draws are scissored to them. 752 SkIRect stencilSpaceIBounds(clipSpaceIBounds); 753 stencilSpaceIBounds.offset(clipSpaceToStencilOffset); 754 GrClip clip(stencilSpaceIBounds); 755 756 int clipBit = stencilAttachment->bits(); 757 SkASSERT((clipBit <= 16) && "Ganesh only handles 16b or smaller stencil buffers"); 758 clipBit = (1 << (clipBit-1)); 759 760 fDrawTarget->cmmAccess().clearStencilClip(stencilSpaceIBounds, 761 GrReducedClip::kAllIn_InitialState == initialState, rt); 762 763 // walk through each clip element and perform its set op 764 // with the existing clip. 765 for (GrReducedClip::ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) { 766 const Element* element = iter.get(); 767 768 GrPipelineBuilder pipelineBuilder; 769 pipelineBuilder.setClip(clip); 770 pipelineBuilder.setRenderTarget(rt); 771 772 pipelineBuilder.setDisableColorXPFactory(); 773 774 // if the target is MSAA then we want MSAA enabled when the clip is soft 775 if (rt->isStencilBufferMultisampled()) { 776 pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, element->isAA()); 777 } 778 779 bool fillInverted = false; 780 // enabled at bottom of loop 781 fClipMode = kIgnoreClip_StencilClipMode; 782 783 // This will be used to determine whether the clip shape can be rendered into the 784 // stencil with arbitrary stencil settings. 785 GrPathRenderer::StencilSupport stencilSupport; 786 787 GrStrokeInfo stroke(SkStrokeRec::kFill_InitStyle); 788 SkRegion::Op op = element->getOp(); 789 790 GrPathRenderer* pr = nullptr; 791 SkPath clipPath; 792 if (Element::kRect_Type == element->getType()) { 793 stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport; 794 fillInverted = false; 795 } else { 796 element->asPath(&clipPath); 797 fillInverted = clipPath.isInverseFillType(); 798 if (fillInverted) { 799 clipPath.toggleInverseFillType(); 800 } 801 802 SkASSERT(pipelineBuilder.getStencil().isDisabled()); 803 804 GrPathRenderer::CanDrawPathArgs canDrawArgs; 805 canDrawArgs.fShaderCaps = this->getContext()->caps()->shaderCaps(); 806 canDrawArgs.fViewMatrix = &viewMatrix; 807 canDrawArgs.fPath = &clipPath; 808 canDrawArgs.fStroke = &stroke; 809 canDrawArgs.fAntiAlias = false; 810 canDrawArgs.fIsStencilDisabled = pipelineBuilder.getStencil().isDisabled(); 811 canDrawArgs.fIsStencilBufferMSAA = rt->isStencilBufferMultisampled(); 812 813 pr = this->getContext()->drawingManager()->getPathRenderer(canDrawArgs, false, 814 GrPathRendererChain::kStencilOnly_DrawType, 815 &stencilSupport); 816 if (nullptr == pr) { 817 return false; 818 } 819 } 820 821 int passes; 822 GrStencilSettings stencilSettings[GrStencilSettings::kMaxStencilClipPasses]; 823 824 bool canRenderDirectToStencil = 825 GrPathRenderer::kNoRestriction_StencilSupport == stencilSupport; 826 bool canDrawDirectToClip; // Given the renderer, the element, 827 // fill rule, and set operation can 828 // we render the element directly to 829 // stencil bit used for clipping. 830 canDrawDirectToClip = GrStencilSettings::GetClipPasses(op, 831 canRenderDirectToStencil, 832 clipBit, 833 fillInverted, 834 &passes, 835 stencilSettings); 836 837 // draw the element to the client stencil bits if necessary 838 if (!canDrawDirectToClip) { 839 GR_STATIC_CONST_SAME_STENCIL(gDrawToStencil, 840 kIncClamp_StencilOp, 841 kIncClamp_StencilOp, 842 kAlways_StencilFunc, 843 0xffff, 844 0x0000, 845 0xffff); 846 if (Element::kRect_Type == element->getType()) { 847 *pipelineBuilder.stencil() = gDrawToStencil; 848 849 draw_non_aa_rect(fDrawTarget, pipelineBuilder, GrColor_WHITE, viewMatrix, 850 element->getRect()); 851 } else { 852 if (!clipPath.isEmpty()) { 853 if (canRenderDirectToStencil) { 854 *pipelineBuilder.stencil() = gDrawToStencil; 855 856 GrPathRenderer::DrawPathArgs args; 857 args.fTarget = fDrawTarget; 858 args.fResourceProvider = this->getContext()->resourceProvider(); 859 args.fPipelineBuilder = &pipelineBuilder; 860 args.fColor = GrColor_WHITE; 861 args.fViewMatrix = &viewMatrix; 862 args.fPath = &clipPath; 863 args.fStroke = &stroke; 864 args.fAntiAlias = false; 865 pr->drawPath(args); 866 } else { 867 GrPathRenderer::StencilPathArgs args; 868 args.fTarget = fDrawTarget; 869 args.fResourceProvider = this->getContext()->resourceProvider(); 870 args.fPipelineBuilder = &pipelineBuilder; 871 args.fViewMatrix = &viewMatrix; 872 args.fPath = &clipPath; 873 args.fStroke = &stroke; 874 pr->stencilPath(args); 875 } 876 } 877 } 878 } 879 880 // now we modify the clip bit by rendering either the clip 881 // element directly or a bounding rect of the entire clip. 882 fClipMode = kModifyClip_StencilClipMode; 883 for (int p = 0; p < passes; ++p) { 884 *pipelineBuilder.stencil() = stencilSettings[p]; 885 886 if (canDrawDirectToClip) { 887 if (Element::kRect_Type == element->getType()) { 888 draw_non_aa_rect(fDrawTarget, pipelineBuilder, GrColor_WHITE, viewMatrix, 889 element->getRect()); 890 } else { 891 GrPathRenderer::DrawPathArgs args; 892 args.fTarget = fDrawTarget; 893 args.fResourceProvider = this->getContext()->resourceProvider(); 894 args.fPipelineBuilder = &pipelineBuilder; 895 args.fColor = GrColor_WHITE; 896 args.fViewMatrix = &viewMatrix; 897 args.fPath = &clipPath; 898 args.fStroke = &stroke; 899 args.fAntiAlias = false; 900 pr->drawPath(args); 901 } 902 } else { 903 // The view matrix is setup to do clip space -> stencil space translation, so 904 // draw rect in clip space. 905 draw_non_aa_rect(fDrawTarget, pipelineBuilder, GrColor_WHITE, viewMatrix, 906 SkRect::Make(clipSpaceIBounds)); 907 } 908 } 909 } 910 } 911 fClipMode = kRespectClip_StencilClipMode; 912 return true; 913 } 914 915 // mapping of clip-respecting stencil funcs to normal stencil funcs 916 // mapping depends on whether stencil-clipping is in effect. 917 static const GrStencilFunc 918 gSpecialToBasicStencilFunc[2][kClipStencilFuncCount] = { 919 {// Stencil-Clipping is DISABLED, we are effectively always inside the clip 920 // In the Clip Funcs 921 kAlways_StencilFunc, // kAlwaysIfInClip_StencilFunc 922 kEqual_StencilFunc, // kEqualIfInClip_StencilFunc 923 kLess_StencilFunc, // kLessIfInClip_StencilFunc 924 kLEqual_StencilFunc, // kLEqualIfInClip_StencilFunc 925 // Special in the clip func that forces user's ref to be 0. 926 kNotEqual_StencilFunc, // kNonZeroIfInClip_StencilFunc 927 // make ref 0 and do normal nequal. 928 }, 929 {// Stencil-Clipping is ENABLED 930 // In the Clip Funcs 931 kEqual_StencilFunc, // kAlwaysIfInClip_StencilFunc 932 // eq stencil clip bit, mask 933 // out user bits. 934 935 kEqual_StencilFunc, // kEqualIfInClip_StencilFunc 936 // add stencil bit to mask and ref 937 938 kLess_StencilFunc, // kLessIfInClip_StencilFunc 939 kLEqual_StencilFunc, // kLEqualIfInClip_StencilFunc 940 // for both of these we can add 941 // the clip bit to the mask and 942 // ref and compare as normal 943 // Special in the clip func that forces user's ref to be 0. 944 kLess_StencilFunc, // kNonZeroIfInClip_StencilFunc 945 // make ref have only the clip bit set 946 // and make comparison be less 947 // 10..0 < 1..user_bits.. 948 } 949 }; 950 951 namespace { 952 // Sets the settings to clip against the stencil buffer clip while ignoring the 953 // client bits. 954 const GrStencilSettings& basic_apply_stencil_clip_settings() { 955 // stencil settings to use when clip is in stencil 956 GR_STATIC_CONST_SAME_STENCIL_STRUCT(gSettings, 957 kKeep_StencilOp, 958 kKeep_StencilOp, 959 kAlwaysIfInClip_StencilFunc, 960 0x0000, 961 0x0000, 962 0x0000); 963 return *GR_CONST_STENCIL_SETTINGS_PTR_FROM_STRUCT_PTR(&gSettings); 964 } 965 } 966 967 void GrClipMaskManager::setPipelineBuilderStencil(const GrPipelineBuilder& pipelineBuilder, 968 GrPipelineBuilder::AutoRestoreStencil* ars) { 969 // We make two copies of the StencilSettings here (except in the early 970 // exit scenario. One copy from draw state to the stack var. Then another 971 // from the stack var to the gpu. We could make this class hold a ptr to 972 // GrGpu's fStencilSettings and eliminate the stack copy here. 973 974 // use stencil for clipping if clipping is enabled and the clip 975 // has been written into the stencil. 976 GrStencilSettings settings; 977 978 // The GrGpu client may not be using the stencil buffer but we may need to 979 // enable it in order to respect a stencil clip. 980 if (pipelineBuilder.getStencil().isDisabled()) { 981 if (GrClipMaskManager::kRespectClip_StencilClipMode == fClipMode) { 982 settings = basic_apply_stencil_clip_settings(); 983 } else { 984 return; 985 } 986 } else { 987 settings = pipelineBuilder.getStencil(); 988 } 989 990 int stencilBits = 0; 991 GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); 992 GrStencilAttachment* stencilAttachment = this->resourceProvider()->attachStencilAttachment(rt); 993 if (stencilAttachment) { 994 stencilBits = stencilAttachment->bits(); 995 } 996 997 SkASSERT(this->caps()->stencilWrapOpsSupport() || !settings.usesWrapOp()); 998 SkASSERT(this->caps()->twoSidedStencilSupport() || !settings.isTwoSided()); 999 this->adjustStencilParams(&settings, fClipMode, stencilBits); 1000 ars->set(&pipelineBuilder); 1001 ars->setStencil(settings); 1002 } 1003 1004 void GrClipMaskManager::adjustStencilParams(GrStencilSettings* settings, 1005 StencilClipMode mode, 1006 int stencilBitCnt) { 1007 SkASSERT(stencilBitCnt > 0); 1008 1009 if (kModifyClip_StencilClipMode == mode) { 1010 // We assume that this clip manager itself is drawing to the GrGpu and 1011 // has already setup the correct values. 1012 return; 1013 } 1014 1015 unsigned int clipBit = (1 << (stencilBitCnt - 1)); 1016 unsigned int userBits = clipBit - 1; 1017 1018 GrStencilSettings::Face face = GrStencilSettings::kFront_Face; 1019 bool twoSided = this->caps()->twoSidedStencilSupport(); 1020 1021 bool finished = false; 1022 while (!finished) { 1023 GrStencilFunc func = settings->func(face); 1024 uint16_t writeMask = settings->writeMask(face); 1025 uint16_t funcMask = settings->funcMask(face); 1026 uint16_t funcRef = settings->funcRef(face); 1027 1028 SkASSERT((unsigned) func < kStencilFuncCount); 1029 1030 writeMask &= userBits; 1031 1032 if (func >= kBasicStencilFuncCount) { 1033 int respectClip = kRespectClip_StencilClipMode == mode; 1034 if (respectClip) { 1035 switch (func) { 1036 case kAlwaysIfInClip_StencilFunc: 1037 funcMask = clipBit; 1038 funcRef = clipBit; 1039 break; 1040 case kEqualIfInClip_StencilFunc: 1041 case kLessIfInClip_StencilFunc: 1042 case kLEqualIfInClip_StencilFunc: 1043 funcMask = (funcMask & userBits) | clipBit; 1044 funcRef = (funcRef & userBits) | clipBit; 1045 break; 1046 case kNonZeroIfInClip_StencilFunc: 1047 funcMask = (funcMask & userBits) | clipBit; 1048 funcRef = clipBit; 1049 break; 1050 default: 1051 SkFAIL("Unknown stencil func"); 1052 } 1053 } else { 1054 funcMask &= userBits; 1055 funcRef &= userBits; 1056 } 1057 const GrStencilFunc* table = 1058 gSpecialToBasicStencilFunc[respectClip]; 1059 func = table[func - kBasicStencilFuncCount]; 1060 SkASSERT(func >= 0 && func < kBasicStencilFuncCount); 1061 } else { 1062 funcMask &= userBits; 1063 funcRef &= userBits; 1064 } 1065 1066 settings->setFunc(face, func); 1067 settings->setWriteMask(face, writeMask); 1068 settings->setFuncMask(face, funcMask); 1069 settings->setFuncRef(face, funcRef); 1070 1071 if (GrStencilSettings::kFront_Face == face) { 1072 face = GrStencilSettings::kBack_Face; 1073 finished = !twoSided; 1074 } else { 1075 finished = true; 1076 } 1077 } 1078 if (!twoSided) { 1079 settings->copyFrontSettingsToBack(); 1080 } 1081 } 1082 1083 //////////////////////////////////////////////////////////////////////////////// 1084 GrTexture* GrClipMaskManager::createSoftwareClipMask(int32_t elementsGenID, 1085 GrReducedClip::InitialState initialState, 1086 const GrReducedClip::ElementList& elements, 1087 const SkVector& clipToMaskOffset, 1088 const SkIRect& clipSpaceIBounds) { 1089 GrUniqueKey key; 1090 GetClipMaskKey(elementsGenID, clipSpaceIBounds, &key); 1091 GrResourceProvider* resourceProvider = this->resourceProvider(); 1092 if (GrTexture* texture = resourceProvider->findAndRefTextureByUniqueKey(key)) { 1093 return texture; 1094 } 1095 1096 // The mask texture may be larger than necessary. We round out the clip space bounds and pin 1097 // the top left corner of the resulting rect to the top left of the texture. 1098 SkIRect maskSpaceIBounds = SkIRect::MakeWH(clipSpaceIBounds.width(), clipSpaceIBounds.height()); 1099 1100 GrSWMaskHelper helper(this->getContext()); 1101 1102 // Set the matrix so that rendered clip elements are transformed to mask space from clip 1103 // space. 1104 SkMatrix translate; 1105 translate.setTranslate(clipToMaskOffset); 1106 1107 helper.init(maskSpaceIBounds, &translate, false); 1108 helper.clear(GrReducedClip::kAllIn_InitialState == initialState ? 0xFF : 0x00); 1109 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); 1110 1111 for (GrReducedClip::ElementList::Iter iter(elements.headIter()) ; iter.get(); iter.next()) { 1112 const Element* element = iter.get(); 1113 SkRegion::Op op = element->getOp(); 1114 1115 if (SkRegion::kIntersect_Op == op || SkRegion::kReverseDifference_Op == op) { 1116 // Intersect and reverse difference require modifying pixels outside of the geometry 1117 // that is being "drawn". In both cases we erase all the pixels outside of the geometry 1118 // but leave the pixels inside the geometry alone. For reverse difference we invert all 1119 // the pixels before clearing the ones outside the geometry. 1120 if (SkRegion::kReverseDifference_Op == op) { 1121 SkRect temp = SkRect::Make(clipSpaceIBounds); 1122 // invert the entire scene 1123 helper.draw(temp, SkRegion::kXOR_Op, false, 0xFF); 1124 } 1125 SkPath clipPath; 1126 element->asPath(&clipPath); 1127 clipPath.toggleInverseFillType(); 1128 helper.draw(clipPath, stroke, SkRegion::kReplace_Op, element->isAA(), 0x00); 1129 continue; 1130 } 1131 1132 // The other ops (union, xor, diff) only affect pixels inside 1133 // the geometry so they can just be drawn normally 1134 if (Element::kRect_Type == element->getType()) { 1135 helper.draw(element->getRect(), op, element->isAA(), 0xFF); 1136 } else { 1137 SkPath path; 1138 element->asPath(&path); 1139 helper.draw(path, stroke, op, element->isAA(), 0xFF); 1140 } 1141 } 1142 1143 // Allocate clip mask texture 1144 GrTexture* result = this->createCachedMask(clipSpaceIBounds.width(), clipSpaceIBounds.height(), 1145 key, false); 1146 if (nullptr == result) { 1147 return nullptr; 1148 } 1149 helper.toTexture(result); 1150 1151 return result; 1152 } 1153 1154 //////////////////////////////////////////////////////////////////////////////// 1155 1156 void GrClipMaskManager::adjustPathStencilParams(const GrStencilAttachment* stencilAttachment, 1157 GrStencilSettings* settings) { 1158 if (stencilAttachment) { 1159 int stencilBits = stencilAttachment->bits(); 1160 this->adjustStencilParams(settings, fClipMode, stencilBits); 1161 } 1162 } 1163