GrContext.cpp revision 2fd42c471c77f54ace35c13975651e17d5b2e8c6
1 2/* 3 * Copyright 2011 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9 10#include "GrContext.h" 11 12#include "effects/GrConvolutionEffect.h" 13#include "effects/GrSingleTextureEffect.h" 14#include "effects/GrConfigConversionEffect.h" 15 16#include "GrBufferAllocPool.h" 17#include "GrGpu.h" 18#include "GrDrawTargetCaps.h" 19#include "GrIndexBuffer.h" 20#include "GrInOrderDrawBuffer.h" 21#include "GrOvalRenderer.h" 22#include "GrPathRenderer.h" 23#include "GrPathUtils.h" 24#include "GrResourceCache.h" 25#include "GrSoftwarePathRenderer.h" 26#include "GrStencilBuffer.h" 27#include "GrTextStrike.h" 28#include "SkRTConf.h" 29#include "SkStrokeRec.h" 30#include "SkTLazy.h" 31#include "SkTLS.h" 32#include "SkTrace.h" 33 34SK_DEFINE_INST_COUNT(GrContext) 35SK_DEFINE_INST_COUNT(GrDrawState) 36 37// It can be useful to set this to false to test whether a bug is caused by using the 38// InOrderDrawBuffer, to compare performance of using/not using InOrderDrawBuffer, or to make 39// debugging simpler. 40SK_CONF_DECLARE(bool, c_Defer, "gpu.deferContext", true, 41 "Defers rendering in GrContext via GrInOrderDrawBuffer."); 42 43#define BUFFERED_DRAW (c_Defer ? kYes_BufferedDraw : kNo_BufferedDraw) 44 45#define MAX_BLUR_SIGMA 4.0f 46 47// When we're using coverage AA but the blend is incompatible (given gpu 48// limitations) should we disable AA or draw wrong? 49#define DISABLE_COVERAGE_AA_FOR_BLEND 1 50 51#if GR_DEBUG 52 // change this to a 1 to see notifications when partial coverage fails 53 #define GR_DEBUG_PARTIAL_COVERAGE_CHECK 0 54#else 55 #define GR_DEBUG_PARTIAL_COVERAGE_CHECK 0 56#endif 57 58static const size_t MAX_TEXTURE_CACHE_COUNT = 2048; 59static const size_t MAX_TEXTURE_CACHE_BYTES = GR_DEFAULT_TEXTURE_CACHE_MB_LIMIT * 1024 * 1024; 60 61static const size_t DRAW_BUFFER_VBPOOL_BUFFER_SIZE = 1 << 15; 62static const int DRAW_BUFFER_VBPOOL_PREALLOC_BUFFERS = 4; 63 64static const size_t DRAW_BUFFER_IBPOOL_BUFFER_SIZE = 1 << 11; 65static const int DRAW_BUFFER_IBPOOL_PREALLOC_BUFFERS = 4; 66 67#define ASSERT_OWNED_RESOURCE(R) GrAssert(!(R) || (R)->getContext() == this) 68 69GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext) { 70 GrContext* context = SkNEW(GrContext); 71 if (context->init(backend, backendContext)) { 72 return context; 73 } else { 74 context->unref(); 75 return NULL; 76 } 77} 78 79namespace { 80void* CreateThreadInstanceCount() { 81 return SkNEW_ARGS(int, (0)); 82} 83void DeleteThreadInstanceCount(void* v) { 84 delete reinterpret_cast<int*>(v); 85} 86#define THREAD_INSTANCE_COUNT \ 87 (*reinterpret_cast<int*>(SkTLS::Get(CreateThreadInstanceCount, DeleteThreadInstanceCount))) 88} 89 90GrContext::GrContext() { 91 ++THREAD_INSTANCE_COUNT; 92 fDrawState = NULL; 93 fGpu = NULL; 94 fPathRendererChain = NULL; 95 fSoftwarePathRenderer = NULL; 96 fTextureCache = NULL; 97 fFontCache = NULL; 98 fDrawBuffer = NULL; 99 fDrawBufferVBAllocPool = NULL; 100 fDrawBufferIBAllocPool = NULL; 101 fAARectRenderer = NULL; 102 fOvalRenderer = NULL; 103} 104 105bool GrContext::init(GrBackend backend, GrBackendContext backendContext) { 106 GrAssert(NULL == fGpu); 107 108 fGpu = GrGpu::Create(backend, backendContext, this); 109 if (NULL == fGpu) { 110 return false; 111 } 112 113 fDrawState = SkNEW(GrDrawState); 114 fGpu->setDrawState(fDrawState); 115 116 117 fTextureCache = SkNEW_ARGS(GrResourceCache, 118 (MAX_TEXTURE_CACHE_COUNT, 119 MAX_TEXTURE_CACHE_BYTES)); 120 fFontCache = SkNEW_ARGS(GrFontCache, (fGpu)); 121 122 fLastDrawWasBuffered = kNo_BufferedDraw; 123 124 fAARectRenderer = SkNEW(GrAARectRenderer); 125 fOvalRenderer = SkNEW(GrOvalRenderer); 126 127 fDidTestPMConversions = false; 128 129 this->setupDrawBuffer(); 130 131 return true; 132} 133 134int GrContext::GetThreadInstanceCount() { 135 return THREAD_INSTANCE_COUNT; 136} 137 138GrContext::~GrContext() { 139 for (int i = 0; i < fCleanUpData.count(); ++i) { 140 (*fCleanUpData[i].fFunc)(this, fCleanUpData[i].fInfo); 141 } 142 143 if (NULL == fGpu) { 144 return; 145 } 146 147 this->flush(); 148 149 // Since the gpu can hold scratch textures, give it a chance to let go 150 // of them before freeing the texture cache 151 fGpu->purgeResources(); 152 153 delete fTextureCache; 154 fTextureCache = NULL; 155 delete fFontCache; 156 delete fDrawBuffer; 157 delete fDrawBufferVBAllocPool; 158 delete fDrawBufferIBAllocPool; 159 160 fAARectRenderer->unref(); 161 fOvalRenderer->unref(); 162 163 fGpu->unref(); 164 GrSafeUnref(fPathRendererChain); 165 GrSafeUnref(fSoftwarePathRenderer); 166 fDrawState->unref(); 167 168 --THREAD_INSTANCE_COUNT; 169} 170 171void GrContext::contextLost() { 172 this->contextDestroyed(); 173 this->setupDrawBuffer(); 174} 175 176void GrContext::contextDestroyed() { 177 // abandon first to so destructors 178 // don't try to free the resources in the API. 179 fGpu->abandonResources(); 180 181 // a path renderer may be holding onto resources that 182 // are now unusable 183 GrSafeSetNull(fPathRendererChain); 184 GrSafeSetNull(fSoftwarePathRenderer); 185 186 delete fDrawBuffer; 187 fDrawBuffer = NULL; 188 189 delete fDrawBufferVBAllocPool; 190 fDrawBufferVBAllocPool = NULL; 191 192 delete fDrawBufferIBAllocPool; 193 fDrawBufferIBAllocPool = NULL; 194 195 fAARectRenderer->reset(); 196 197 fTextureCache->purgeAllUnlocked(); 198 fFontCache->freeAll(); 199 fGpu->markContextDirty(); 200} 201 202void GrContext::resetContext() { 203 fGpu->markContextDirty(); 204} 205 206void GrContext::freeGpuResources() { 207 this->flush(); 208 209 fGpu->purgeResources(); 210 211 fAARectRenderer->reset(); 212 213 fTextureCache->purgeAllUnlocked(); 214 fFontCache->freeAll(); 215 // a path renderer may be holding onto resources 216 GrSafeSetNull(fPathRendererChain); 217 GrSafeSetNull(fSoftwarePathRenderer); 218} 219 220size_t GrContext::getGpuTextureCacheBytes() const { 221 return fTextureCache->getCachedResourceBytes(); 222} 223 224//////////////////////////////////////////////////////////////////////////////// 225 226namespace { 227 228void scale_rect(SkRect* rect, float xScale, float yScale) { 229 rect->fLeft = SkScalarMul(rect->fLeft, SkFloatToScalar(xScale)); 230 rect->fTop = SkScalarMul(rect->fTop, SkFloatToScalar(yScale)); 231 rect->fRight = SkScalarMul(rect->fRight, SkFloatToScalar(xScale)); 232 rect->fBottom = SkScalarMul(rect->fBottom, SkFloatToScalar(yScale)); 233} 234 235float adjust_sigma(float sigma, int *scaleFactor, int *radius) { 236 *scaleFactor = 1; 237 while (sigma > MAX_BLUR_SIGMA) { 238 *scaleFactor *= 2; 239 sigma *= 0.5f; 240 } 241 *radius = static_cast<int>(ceilf(sigma * 3.0f)); 242 GrAssert(*radius <= GrConvolutionEffect::kMaxKernelRadius); 243 return sigma; 244} 245 246void convolve_gaussian(GrDrawTarget* target, 247 GrTexture* texture, 248 const SkRect& rect, 249 float sigma, 250 int radius, 251 Gr1DKernelEffect::Direction direction) { 252 GrRenderTarget* rt = target->drawState()->getRenderTarget(); 253 GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kReset_ASRInit); 254 GrDrawState* drawState = target->drawState(); 255 drawState->setRenderTarget(rt); 256 SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(texture, 257 direction, 258 radius, 259 sigma)); 260 drawState->setEffect(0, conv); 261 target->drawSimpleRect(rect, NULL); 262} 263 264} 265 266//////////////////////////////////////////////////////////////////////////////// 267 268GrTexture* GrContext::findAndRefTexture(const GrTextureDesc& desc, 269 const GrCacheID& cacheID, 270 const GrTextureParams* params) { 271 GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID); 272 GrResource* resource = fTextureCache->find(resourceKey); 273 SkSafeRef(resource); 274 return static_cast<GrTexture*>(resource); 275} 276 277bool GrContext::isTextureInCache(const GrTextureDesc& desc, 278 const GrCacheID& cacheID, 279 const GrTextureParams* params) const { 280 GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID); 281 return fTextureCache->hasKey(resourceKey); 282} 283 284void GrContext::addStencilBuffer(GrStencilBuffer* sb) { 285 ASSERT_OWNED_RESOURCE(sb); 286 287 GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(sb->width(), 288 sb->height(), 289 sb->numSamples()); 290 fTextureCache->addResource(resourceKey, sb); 291} 292 293GrStencilBuffer* GrContext::findStencilBuffer(int width, int height, 294 int sampleCnt) { 295 GrResourceKey resourceKey = GrStencilBuffer::ComputeKey(width, 296 height, 297 sampleCnt); 298 GrResource* resource = fTextureCache->find(resourceKey); 299 return static_cast<GrStencilBuffer*>(resource); 300} 301 302static void stretchImage(void* dst, 303 int dstW, 304 int dstH, 305 void* src, 306 int srcW, 307 int srcH, 308 int bpp) { 309 GrFixed dx = (srcW << 16) / dstW; 310 GrFixed dy = (srcH << 16) / dstH; 311 312 GrFixed y = dy >> 1; 313 314 int dstXLimit = dstW*bpp; 315 for (int j = 0; j < dstH; ++j) { 316 GrFixed x = dx >> 1; 317 void* srcRow = (uint8_t*)src + (y>>16)*srcW*bpp; 318 void* dstRow = (uint8_t*)dst + j*dstW*bpp; 319 for (int i = 0; i < dstXLimit; i += bpp) { 320 memcpy((uint8_t*) dstRow + i, 321 (uint8_t*) srcRow + (x>>16)*bpp, 322 bpp); 323 x += dx; 324 } 325 y += dy; 326 } 327} 328 329namespace { 330 331// position + local coordinate 332extern const GrVertexAttrib gVertexAttribs[] = { 333 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 334 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding} 335}; 336 337}; 338 339// The desired texture is NPOT and tiled but that isn't supported by 340// the current hardware. Resize the texture to be a POT 341GrTexture* GrContext::createResizedTexture(const GrTextureDesc& desc, 342 const GrCacheID& cacheID, 343 void* srcData, 344 size_t rowBytes, 345 bool needsFiltering) { 346 SkAutoTUnref<GrTexture> clampedTexture(this->findAndRefTexture(desc, cacheID, NULL)); 347 if (NULL == clampedTexture) { 348 clampedTexture.reset(this->createTexture(NULL, desc, cacheID, srcData, rowBytes)); 349 350 if (NULL == clampedTexture) { 351 return NULL; 352 } 353 } 354 355 GrTextureDesc rtDesc = desc; 356 rtDesc.fFlags = rtDesc.fFlags | 357 kRenderTarget_GrTextureFlagBit | 358 kNoStencil_GrTextureFlagBit; 359 rtDesc.fWidth = GrNextPow2(GrMax(desc.fWidth, 64)); 360 rtDesc.fHeight = GrNextPow2(GrMax(desc.fHeight, 64)); 361 362 GrTexture* texture = fGpu->createTexture(rtDesc, NULL, 0); 363 364 if (NULL != texture) { 365 GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit); 366 GrDrawState* drawState = fGpu->drawState(); 367 drawState->setRenderTarget(texture->asRenderTarget()); 368 369 // if filtering is not desired then we want to ensure all 370 // texels in the resampled image are copies of texels from 371 // the original. 372 GrTextureParams params(SkShader::kClamp_TileMode, needsFiltering); 373 drawState->createTextureEffect(0, clampedTexture, SkMatrix::I(), params); 374 375 drawState->setVertexAttribs<gVertexAttribs>(SK_ARRAY_COUNT(gVertexAttribs)); 376 377 GrDrawTarget::AutoReleaseGeometry arg(fGpu, 4, 0); 378 379 if (arg.succeeded()) { 380 GrPoint* verts = (GrPoint*) arg.vertices(); 381 verts[0].setIRectFan(0, 0, texture->width(), texture->height(), 2 * sizeof(GrPoint)); 382 verts[1].setIRectFan(0, 0, 1, 1, 2 * sizeof(GrPoint)); 383 fGpu->drawNonIndexed(kTriangleFan_GrPrimitiveType, 0, 4); 384 } 385 } else { 386 // TODO: Our CPU stretch doesn't filter. But we create separate 387 // stretched textures when the texture params is either filtered or 388 // not. Either implement filtered stretch blit on CPU or just create 389 // one when FBO case fails. 390 391 rtDesc.fFlags = kNone_GrTextureFlags; 392 // no longer need to clamp at min RT size. 393 rtDesc.fWidth = GrNextPow2(desc.fWidth); 394 rtDesc.fHeight = GrNextPow2(desc.fHeight); 395 int bpp = GrBytesPerPixel(desc.fConfig); 396 SkAutoSMalloc<128*128*4> stretchedPixels(bpp * rtDesc.fWidth * rtDesc.fHeight); 397 stretchImage(stretchedPixels.get(), rtDesc.fWidth, rtDesc.fHeight, 398 srcData, desc.fWidth, desc.fHeight, bpp); 399 400 size_t stretchedRowBytes = rtDesc.fWidth * bpp; 401 402 SkDEBUGCODE(GrTexture* texture = )fGpu->createTexture(rtDesc, stretchedPixels.get(), 403 stretchedRowBytes); 404 GrAssert(NULL != texture); 405 } 406 407 return texture; 408} 409 410GrTexture* GrContext::createTexture(const GrTextureParams* params, 411 const GrTextureDesc& desc, 412 const GrCacheID& cacheID, 413 void* srcData, 414 size_t rowBytes) { 415 SK_TRACE_EVENT0("GrContext::createTexture"); 416 417 GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID); 418 419 GrTexture* texture; 420 if (GrTexture::NeedsResizing(resourceKey)) { 421 texture = this->createResizedTexture(desc, cacheID, 422 srcData, rowBytes, 423 GrTexture::NeedsFiltering(resourceKey)); 424 } else { 425 texture= fGpu->createTexture(desc, srcData, rowBytes); 426 } 427 428 if (NULL != texture) { 429 fTextureCache->addResource(resourceKey, texture); 430 } 431 432 return texture; 433} 434 435GrTexture* GrContext::lockAndRefScratchTexture(const GrTextureDesc& inDesc, ScratchTexMatch match) { 436 GrTextureDesc desc = inDesc; 437 438 GrAssert((desc.fFlags & kRenderTarget_GrTextureFlagBit) || 439 !(desc.fFlags & kNoStencil_GrTextureFlagBit)); 440 441 if (kApprox_ScratchTexMatch == match) { 442 // bin by pow2 with a reasonable min 443 static const int MIN_SIZE = 16; 444 desc.fWidth = GrMax(MIN_SIZE, GrNextPow2(desc.fWidth)); 445 desc.fHeight = GrMax(MIN_SIZE, GrNextPow2(desc.fHeight)); 446 } 447 448 // Renderable A8 targets are not universally supported (e.g., not on ANGLE) 449 GrAssert(this->isConfigRenderable(kAlpha_8_GrPixelConfig) || 450 !(desc.fFlags & kRenderTarget_GrTextureFlagBit) || 451 (desc.fConfig != kAlpha_8_GrPixelConfig)); 452 453 GrResource* resource = NULL; 454 int origWidth = desc.fWidth; 455 int origHeight = desc.fHeight; 456 457 do { 458 GrResourceKey key = GrTexture::ComputeScratchKey(desc); 459 // Ensure we have exclusive access to the texture so future 'find' calls don't return it 460 resource = fTextureCache->find(key, GrResourceCache::kHide_OwnershipFlag); 461 if (NULL != resource) { 462 resource->ref(); 463 break; 464 } 465 if (kExact_ScratchTexMatch == match) { 466 break; 467 } 468 // We had a cache miss and we are in approx mode, relax the fit of the flags. 469 470 // We no longer try to reuse textures that were previously used as render targets in 471 // situations where no RT is needed; doing otherwise can confuse the video driver and 472 // cause significant performance problems in some cases. 473 if (desc.fFlags & kNoStencil_GrTextureFlagBit) { 474 desc.fFlags = desc.fFlags & ~kNoStencil_GrTextureFlagBit; 475 } else { 476 break; 477 } 478 479 } while (true); 480 481 if (NULL == resource) { 482 desc.fFlags = inDesc.fFlags; 483 desc.fWidth = origWidth; 484 desc.fHeight = origHeight; 485 GrTexture* texture = fGpu->createTexture(desc, NULL, 0); 486 if (NULL != texture) { 487 GrResourceKey key = GrTexture::ComputeScratchKey(texture->desc()); 488 // Make the resource exclusive so future 'find' calls don't return it 489 fTextureCache->addResource(key, texture, GrResourceCache::kHide_OwnershipFlag); 490 resource = texture; 491 } 492 } 493 494 return static_cast<GrTexture*>(resource); 495} 496 497void GrContext::addExistingTextureToCache(GrTexture* texture) { 498 499 if (NULL == texture) { 500 return; 501 } 502 503 // This texture should already have a cache entry since it was once 504 // attached 505 GrAssert(NULL != texture->getCacheEntry()); 506 507 // Conceptually, the cache entry is going to assume responsibility 508 // for the creation ref. 509 GrAssert(1 == texture->getRefCnt()); 510 511 // Since this texture came from an AutoScratchTexture it should 512 // still be in the exclusive pile 513 fTextureCache->makeNonExclusive(texture->getCacheEntry()); 514 515 this->purgeCache(); 516} 517 518 519void GrContext::unlockScratchTexture(GrTexture* texture) { 520 ASSERT_OWNED_RESOURCE(texture); 521 GrAssert(NULL != texture->getCacheEntry()); 522 523 // If this is a scratch texture we detached it from the cache 524 // while it was locked (to avoid two callers simultaneously getting 525 // the same texture). 526 if (texture->getCacheEntry()->key().isScratch()) { 527 fTextureCache->makeNonExclusive(texture->getCacheEntry()); 528 } 529 530 this->purgeCache(); 531} 532 533void GrContext::purgeCache() { 534 if (NULL != fTextureCache) { 535 fTextureCache->purgeAsNeeded(); 536 } 537} 538 539GrTexture* GrContext::createUncachedTexture(const GrTextureDesc& descIn, 540 void* srcData, 541 size_t rowBytes) { 542 GrTextureDesc descCopy = descIn; 543 return fGpu->createTexture(descCopy, srcData, rowBytes); 544} 545 546void GrContext::getTextureCacheLimits(int* maxTextures, 547 size_t* maxTextureBytes) const { 548 fTextureCache->getLimits(maxTextures, maxTextureBytes); 549} 550 551void GrContext::setTextureCacheLimits(int maxTextures, size_t maxTextureBytes) { 552 fTextureCache->setLimits(maxTextures, maxTextureBytes); 553} 554 555int GrContext::getMaxTextureSize() const { 556 return fGpu->caps()->maxTextureSize(); 557} 558 559int GrContext::getMaxRenderTargetSize() const { 560 return fGpu->caps()->maxRenderTargetSize(); 561} 562 563int GrContext::getMaxSampleCount() const { 564 return fGpu->caps()->maxSampleCount(); 565} 566 567/////////////////////////////////////////////////////////////////////////////// 568 569GrTexture* GrContext::wrapBackendTexture(const GrBackendTextureDesc& desc) { 570 return fGpu->wrapBackendTexture(desc); 571} 572 573GrRenderTarget* GrContext::wrapBackendRenderTarget(const GrBackendRenderTargetDesc& desc) { 574 return fGpu->wrapBackendRenderTarget(desc); 575} 576 577/////////////////////////////////////////////////////////////////////////////// 578 579bool GrContext::supportsIndex8PixelConfig(const GrTextureParams* params, 580 int width, int height) const { 581 const GrDrawTargetCaps* caps = fGpu->caps(); 582 if (!caps->eightBitPaletteSupport()) { 583 return false; 584 } 585 586 bool isPow2 = GrIsPow2(width) && GrIsPow2(height); 587 588 if (!isPow2) { 589 bool tiled = NULL != params && params->isTiled(); 590 if (tiled && !caps->npotTextureTileSupport()) { 591 return false; 592 } 593 } 594 return true; 595} 596 597//////////////////////////////////////////////////////////////////////////////// 598 599const GrClipData* GrContext::getClip() const { 600 return fGpu->getClip(); 601} 602 603void GrContext::setClip(const GrClipData* clipData) { 604 fGpu->setClip(clipData); 605 606 fDrawState->setState(GrDrawState::kClip_StateBit, 607 clipData && clipData->fClipStack && !clipData->fClipStack->isWideOpen()); 608} 609 610//////////////////////////////////////////////////////////////////////////////// 611 612void GrContext::clear(const GrIRect* rect, 613 const GrColor color, 614 GrRenderTarget* target) { 615 this->prepareToDraw(NULL, BUFFERED_DRAW)->clear(rect, color, target); 616} 617 618void GrContext::drawPaint(const GrPaint& origPaint) { 619 // set rect to be big enough to fill the space, but not super-huge, so we 620 // don't overflow fixed-point implementations 621 GrRect r; 622 r.setLTRB(0, 0, 623 SkIntToScalar(getRenderTarget()->width()), 624 SkIntToScalar(getRenderTarget()->height())); 625 SkMatrix inverse; 626 SkTCopyOnFirstWrite<GrPaint> paint(origPaint); 627 AutoMatrix am; 628 629 // We attempt to map r by the inverse matrix and draw that. mapRect will 630 // map the four corners and bound them with a new rect. This will not 631 // produce a correct result for some perspective matrices. 632 if (!this->getMatrix().hasPerspective()) { 633 if (!fDrawState->getViewInverse(&inverse)) { 634 GrPrintf("Could not invert matrix\n"); 635 return; 636 } 637 inverse.mapRect(&r); 638 } else { 639 if (!am.setIdentity(this, paint.writable())) { 640 GrPrintf("Could not invert matrix\n"); 641 return; 642 } 643 } 644 // by definition this fills the entire clip, no need for AA 645 if (paint->isAntiAlias()) { 646 paint.writable()->setAntiAlias(false); 647 } 648 this->drawRect(*paint, r); 649} 650 651//////////////////////////////////////////////////////////////////////////////// 652 653namespace { 654inline bool disable_coverage_aa_for_blend(GrDrawTarget* target) { 655 return DISABLE_COVERAGE_AA_FOR_BLEND && !target->canApplyCoverage(); 656} 657} 658 659//////////////////////////////////////////////////////////////////////////////// 660 661/* create a triangle strip that strokes the specified triangle. There are 8 662 unique vertices, but we repreat the last 2 to close up. Alternatively we 663 could use an indices array, and then only send 8 verts, but not sure that 664 would be faster. 665 */ 666static void setStrokeRectStrip(GrPoint verts[10], GrRect rect, 667 SkScalar width) { 668 const SkScalar rad = SkScalarHalf(width); 669 rect.sort(); 670 671 verts[0].set(rect.fLeft + rad, rect.fTop + rad); 672 verts[1].set(rect.fLeft - rad, rect.fTop - rad); 673 verts[2].set(rect.fRight - rad, rect.fTop + rad); 674 verts[3].set(rect.fRight + rad, rect.fTop - rad); 675 verts[4].set(rect.fRight - rad, rect.fBottom - rad); 676 verts[5].set(rect.fRight + rad, rect.fBottom + rad); 677 verts[6].set(rect.fLeft + rad, rect.fBottom - rad); 678 verts[7].set(rect.fLeft - rad, rect.fBottom + rad); 679 verts[8] = verts[0]; 680 verts[9] = verts[1]; 681} 682 683/** 684 * Returns true if the rects edges are integer-aligned. 685 */ 686static bool isIRect(const GrRect& r) { 687 return SkScalarIsInt(r.fLeft) && SkScalarIsInt(r.fTop) && 688 SkScalarIsInt(r.fRight) && SkScalarIsInt(r.fBottom); 689} 690 691static bool apply_aa_to_rect(GrDrawTarget* target, 692 const GrRect& rect, 693 SkScalar strokeWidth, 694 const SkMatrix* matrix, 695 SkMatrix* combinedMatrix, 696 GrRect* devRect, 697 bool* useVertexCoverage) { 698 // we use a simple coverage ramp to do aa on axis-aligned rects 699 // we check if the rect will be axis-aligned, and the rect won't land on 700 // integer coords. 701 702 // we are keeping around the "tweak the alpha" trick because 703 // it is our only hope for the fixed-pipe implementation. 704 // In a shader implementation we can give a separate coverage input 705 // TODO: remove this ugliness when we drop the fixed-pipe impl 706 *useVertexCoverage = false; 707 if (!target->getDrawState().canTweakAlphaForCoverage()) { 708 if (disable_coverage_aa_for_blend(target)) { 709#if GR_DEBUG 710 //GrPrintf("Turning off AA to correctly apply blend.\n"); 711#endif 712 return false; 713 } else { 714 *useVertexCoverage = true; 715 } 716 } 717 const GrDrawState& drawState = target->getDrawState(); 718 if (drawState.getRenderTarget()->isMultisampled()) { 719 return false; 720 } 721 722 if (0 == strokeWidth && target->willUseHWAALines()) { 723 return false; 724 } 725 726#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) 727 if (strokeWidth >= 0) { 728#endif 729 if (!drawState.getViewMatrix().preservesAxisAlignment()) { 730 return false; 731 } 732 733 if (NULL != matrix && !matrix->preservesAxisAlignment()) { 734 return false; 735 } 736#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) 737 } else { 738 if (!drawState.getViewMatrix().preservesAxisAlignment() && 739 !drawState.getViewMatrix().preservesRightAngles()) { 740 return false; 741 } 742 743 if (NULL != matrix && !matrix->preservesRightAngles()) { 744 return false; 745 } 746 } 747#endif 748 749 *combinedMatrix = drawState.getViewMatrix(); 750 if (NULL != matrix) { 751 combinedMatrix->preConcat(*matrix); 752 753#if GR_DEBUG 754#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) 755 if (strokeWidth >= 0) { 756#endif 757 GrAssert(combinedMatrix->preservesAxisAlignment()); 758#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) 759 } else { 760 GrAssert(combinedMatrix->preservesRightAngles()); 761 } 762#endif 763#endif 764 } 765 766 combinedMatrix->mapRect(devRect, rect); 767 768 if (strokeWidth < 0 769#if defined(SHADER_AA_FILL_RECT) || !defined(IGNORE_ROT_AA_RECT_OPT) 770 && drawState.getViewMatrix().preservesAxisAlignment() 771#endif 772 ) { 773 return !isIRect(*devRect); 774 } else { 775 return true; 776 } 777} 778 779void GrContext::drawRect(const GrPaint& paint, 780 const GrRect& rect, 781 SkScalar width, 782 const SkMatrix* matrix) { 783 SK_TRACE_EVENT0("GrContext::drawRect"); 784 785 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 786 GrDrawState::AutoStageDisable atr(fDrawState); 787 788 GrRect devRect; 789 SkMatrix combinedMatrix; 790 bool useVertexCoverage; 791 bool needAA = paint.isAntiAlias() && 792 !this->getRenderTarget()->isMultisampled(); 793 bool doAA = needAA && apply_aa_to_rect(target, rect, width, matrix, 794 &combinedMatrix, &devRect, 795 &useVertexCoverage); 796 if (doAA) { 797 GrDrawState::AutoDeviceCoordDraw adcd(target->drawState()); 798 if (!adcd.succeeded()) { 799 return; 800 } 801 if (width >= 0) { 802 GrVec strokeSize; 803 if (width > 0) { 804 strokeSize.set(width, width); 805 combinedMatrix.mapVectors(&strokeSize, 1); 806 strokeSize.setAbs(strokeSize); 807 } else { 808 strokeSize.set(SK_Scalar1, SK_Scalar1); 809 } 810 fAARectRenderer->strokeAARect(this->getGpu(), target, devRect, 811 strokeSize, useVertexCoverage); 812 } else { 813 // filled AA rect 814 fAARectRenderer->fillAARect(this->getGpu(), target, 815 rect, combinedMatrix, devRect, 816 useVertexCoverage); 817 } 818 return; 819 } 820 821 if (width >= 0) { 822 // TODO: consider making static vertex buffers for these cases. 823 // Hairline could be done by just adding closing vertex to 824 // unitSquareVertexBuffer() 825 826 static const int worstCaseVertCount = 10; 827 target->drawState()->setDefaultVertexAttribs(); 828 GrDrawTarget::AutoReleaseGeometry geo(target, worstCaseVertCount, 0); 829 830 if (!geo.succeeded()) { 831 GrPrintf("Failed to get space for vertices!\n"); 832 return; 833 } 834 835 GrPrimitiveType primType; 836 int vertCount; 837 GrPoint* vertex = geo.positions(); 838 839 if (width > 0) { 840 vertCount = 10; 841 primType = kTriangleStrip_GrPrimitiveType; 842 setStrokeRectStrip(vertex, rect, width); 843 } else { 844 // hairline 845 vertCount = 5; 846 primType = kLineStrip_GrPrimitiveType; 847 vertex[0].set(rect.fLeft, rect.fTop); 848 vertex[1].set(rect.fRight, rect.fTop); 849 vertex[2].set(rect.fRight, rect.fBottom); 850 vertex[3].set(rect.fLeft, rect.fBottom); 851 vertex[4].set(rect.fLeft, rect.fTop); 852 } 853 854 GrDrawState::AutoViewMatrixRestore avmr; 855 if (NULL != matrix) { 856 GrDrawState* drawState = target->drawState(); 857 avmr.set(drawState, *matrix); 858 } 859 860 target->drawNonIndexed(primType, 0, vertCount); 861 } else { 862 // filled BW rect 863 target->drawSimpleRect(rect, matrix); 864 } 865} 866 867void GrContext::drawRectToRect(const GrPaint& paint, 868 const GrRect& dstRect, 869 const GrRect& localRect, 870 const SkMatrix* dstMatrix, 871 const SkMatrix* localMatrix) { 872 SK_TRACE_EVENT0("GrContext::drawRectToRect"); 873 874 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 875 GrDrawState::AutoStageDisable atr(fDrawState); 876 877 target->drawRect(dstRect, dstMatrix, &localRect, localMatrix); 878} 879 880namespace { 881 882extern const GrVertexAttrib gPosUVColorAttribs[] = { 883 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding }, 884 {kVec2f_GrVertexAttribType, sizeof(GrPoint), kLocalCoord_GrVertexAttribBinding }, 885 {kVec4ub_GrVertexAttribType, 2*sizeof(GrPoint), kColor_GrVertexAttribBinding} 886}; 887 888extern const GrVertexAttrib gPosColorAttribs[] = { 889 {kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding}, 890 {kVec4ub_GrVertexAttribType, sizeof(GrPoint), kColor_GrVertexAttribBinding}, 891}; 892 893static void set_vertex_attributes(GrDrawState* drawState, 894 const GrPoint* texCoords, 895 const GrColor* colors, 896 int* colorOffset, 897 int* texOffset) { 898 *texOffset = -1; 899 *colorOffset = -1; 900 901 if (NULL != texCoords && NULL != colors) { 902 *texOffset = sizeof(GrPoint); 903 *colorOffset = 2*sizeof(GrPoint); 904 drawState->setVertexAttribs<gPosUVColorAttribs>(3); 905 } else if (NULL != texCoords) { 906 *texOffset = sizeof(GrPoint); 907 drawState->setVertexAttribs<gPosUVColorAttribs>(2); 908 } else if (NULL != colors) { 909 *colorOffset = sizeof(GrPoint); 910 drawState->setVertexAttribs<gPosColorAttribs>(2); 911 } else { 912 drawState->setVertexAttribs<gPosColorAttribs>(1); 913 } 914} 915 916}; 917 918void GrContext::drawVertices(const GrPaint& paint, 919 GrPrimitiveType primitiveType, 920 int vertexCount, 921 const GrPoint positions[], 922 const GrPoint texCoords[], 923 const GrColor colors[], 924 const uint16_t indices[], 925 int indexCount) { 926 SK_TRACE_EVENT0("GrContext::drawVertices"); 927 928 GrDrawTarget::AutoReleaseGeometry geo; 929 930 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 931 GrDrawState::AutoStageDisable atr(fDrawState); 932 933 GrDrawState* drawState = target->drawState(); 934 935 int colorOffset = -1, texOffset = -1; 936 set_vertex_attributes(drawState, texCoords, colors, &colorOffset, &texOffset); 937 938 size_t vertexSize = drawState->getVertexSize(); 939 if (sizeof(GrPoint) != vertexSize) { 940 if (!geo.set(target, vertexCount, 0)) { 941 GrPrintf("Failed to get space for vertices!\n"); 942 return; 943 } 944 void* curVertex = geo.vertices(); 945 946 for (int i = 0; i < vertexCount; ++i) { 947 *((GrPoint*)curVertex) = positions[i]; 948 949 if (texOffset >= 0) { 950 *(GrPoint*)((intptr_t)curVertex + texOffset) = texCoords[i]; 951 } 952 if (colorOffset >= 0) { 953 *(GrColor*)((intptr_t)curVertex + colorOffset) = colors[i]; 954 } 955 curVertex = (void*)((intptr_t)curVertex + vertexSize); 956 } 957 } else { 958 target->setVertexSourceToArray(positions, vertexCount); 959 } 960 961 // we don't currently apply offscreen AA to this path. Need improved 962 // management of GrDrawTarget's geometry to avoid copying points per-tile. 963 964 if (NULL != indices) { 965 target->setIndexSourceToArray(indices, indexCount); 966 target->drawIndexed(primitiveType, 0, 0, vertexCount, indexCount); 967 target->resetIndexSource(); 968 } else { 969 target->drawNonIndexed(primitiveType, 0, vertexCount); 970 } 971} 972 973/////////////////////////////////////////////////////////////////////////////// 974 975void GrContext::drawRRect(const GrPaint& paint, 976 const SkRRect& rect, 977 const SkStrokeRec& stroke) { 978 979 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 980 GrDrawState::AutoStageDisable atr(fDrawState); 981 982 bool prAA = paint.isAntiAlias() && !this->getRenderTarget()->isMultisampled(); 983 984 if (!fOvalRenderer->drawSimpleRRect(target, this, prAA, rect, stroke)) { 985 SkPath path; 986 path.addRRect(rect); 987 this->internalDrawPath(target, prAA, path, stroke); 988 } 989} 990 991/////////////////////////////////////////////////////////////////////////////// 992 993void GrContext::drawOval(const GrPaint& paint, 994 const GrRect& oval, 995 const SkStrokeRec& stroke) { 996 997 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 998 GrDrawState::AutoStageDisable atr(fDrawState); 999 1000 bool useAA = paint.isAntiAlias() && !this->getRenderTarget()->isMultisampled(); 1001 1002 if (!fOvalRenderer->drawOval(target, this, useAA, oval, stroke)) { 1003 SkPath path; 1004 path.addOval(oval); 1005 this->internalDrawPath(target, useAA, path, stroke); 1006 } 1007} 1008 1009void GrContext::drawPath(const GrPaint& paint, const SkPath& path, const SkStrokeRec& stroke) { 1010 1011 if (path.isEmpty()) { 1012 if (path.isInverseFillType()) { 1013 this->drawPaint(paint); 1014 } 1015 return; 1016 } 1017 1018 // Note that internalDrawPath may sw-rasterize the path into a scratch texture. 1019 // Scratch textures can be recycled after they are returned to the texture 1020 // cache. This presents a potential hazard for buffered drawing. However, 1021 // the writePixels that uploads to the scratch will perform a flush so we're 1022 // OK. 1023 GrDrawTarget* target = this->prepareToDraw(&paint, BUFFERED_DRAW); 1024 GrDrawState::AutoStageDisable atr(fDrawState); 1025 1026 SkRect ovalRect; 1027 bool isOval = path.isOval(&ovalRect); 1028 bool useAA = paint.isAntiAlias() && !this->getRenderTarget()->isMultisampled(); 1029 1030 if (!isOval || path.isInverseFillType() 1031 || !fOvalRenderer->drawOval(target, this, useAA, ovalRect, stroke)) { 1032 this->internalDrawPath(target, useAA, path, stroke); 1033 } 1034} 1035 1036void GrContext::internalDrawPath(GrDrawTarget* target, bool useAA, const SkPath& path, 1037 const SkStrokeRec& stroke) { 1038 1039 // An Assumption here is that path renderer would use some form of tweaking 1040 // the src color (either the input alpha or in the frag shader) to implement 1041 // aa. If we have some future driver-mojo path AA that can do the right 1042 // thing WRT to the blend then we'll need some query on the PR. 1043 if (disable_coverage_aa_for_blend(target)) { 1044#if GR_DEBUG 1045 //GrPrintf("Turning off AA to correctly apply blend.\n"); 1046#endif 1047 useAA = false; 1048 } 1049 1050 GrPathRendererChain::DrawType type = useAA ? GrPathRendererChain::kColorAntiAlias_DrawType : 1051 GrPathRendererChain::kColor_DrawType; 1052 1053 const SkPath* pathPtr = &path; 1054 SkPath tmpPath; 1055 SkStrokeRec strokeRec(stroke); 1056 1057 // Try a 1st time without stroking the path and without allowing the SW renderer 1058 GrPathRenderer* pr = this->getPathRenderer(*pathPtr, strokeRec, target, false, type); 1059 1060 if (NULL == pr) { 1061 if (!strokeRec.isHairlineStyle()) { 1062 // It didn't work the 1st time, so try again with the stroked path 1063 if (strokeRec.applyToPath(&tmpPath, *pathPtr)) { 1064 pathPtr = &tmpPath; 1065 strokeRec.setFillStyle(); 1066 } 1067 } 1068 // This time, allow SW renderer 1069 pr = this->getPathRenderer(*pathPtr, strokeRec, target, true, type); 1070 } 1071 1072 if (NULL == pr) { 1073#if GR_DEBUG 1074 GrPrintf("Unable to find path renderer compatible with path.\n"); 1075#endif 1076 return; 1077 } 1078 1079 pr->drawPath(*pathPtr, strokeRec, target, useAA); 1080} 1081 1082//////////////////////////////////////////////////////////////////////////////// 1083 1084void GrContext::flush(int flagsBitfield) { 1085 if (kDiscard_FlushBit & flagsBitfield) { 1086 fDrawBuffer->reset(); 1087 } else { 1088 this->flushDrawBuffer(); 1089 } 1090 if (kForceCurrentRenderTarget_FlushBit & flagsBitfield) { 1091 fGpu->forceRenderTargetFlush(); 1092 } 1093} 1094 1095void GrContext::flushDrawBuffer() { 1096 if (NULL != fDrawBuffer && !fDrawBuffer->isFlushing()) { 1097 fDrawBuffer->flush(); 1098 } 1099} 1100 1101bool GrContext::writeTexturePixels(GrTexture* texture, 1102 int left, int top, int width, int height, 1103 GrPixelConfig config, const void* buffer, size_t rowBytes, 1104 uint32_t flags) { 1105 SK_TRACE_EVENT0("GrContext::writeTexturePixels"); 1106 ASSERT_OWNED_RESOURCE(texture); 1107 1108 if ((kUnpremul_PixelOpsFlag & flags) || !fGpu->canWriteTexturePixels(texture, config)) { 1109 if (NULL != texture->asRenderTarget()) { 1110 return this->writeRenderTargetPixels(texture->asRenderTarget(), 1111 left, top, width, height, 1112 config, buffer, rowBytes, flags); 1113 } else { 1114 return false; 1115 } 1116 } 1117 1118 if (!(kDontFlush_PixelOpsFlag & flags)) { 1119 this->flush(); 1120 } 1121 1122 return fGpu->writeTexturePixels(texture, left, top, width, height, 1123 config, buffer, rowBytes); 1124} 1125 1126bool GrContext::readTexturePixels(GrTexture* texture, 1127 int left, int top, int width, int height, 1128 GrPixelConfig config, void* buffer, size_t rowBytes, 1129 uint32_t flags) { 1130 SK_TRACE_EVENT0("GrContext::readTexturePixels"); 1131 ASSERT_OWNED_RESOURCE(texture); 1132 1133 // TODO: code read pixels for textures that aren't also rendertargets 1134 GrRenderTarget* target = texture->asRenderTarget(); 1135 if (NULL != target) { 1136 return this->readRenderTargetPixels(target, 1137 left, top, width, height, 1138 config, buffer, rowBytes, 1139 flags); 1140 } else { 1141 return false; 1142 } 1143} 1144 1145#include "SkConfig8888.h" 1146 1147namespace { 1148/** 1149 * Converts a GrPixelConfig to a SkCanvas::Config8888. Only byte-per-channel 1150 * formats are representable as Config8888 and so the function returns false 1151 * if the GrPixelConfig has no equivalent Config8888. 1152 */ 1153bool grconfig_to_config8888(GrPixelConfig config, 1154 bool unpremul, 1155 SkCanvas::Config8888* config8888) { 1156 switch (config) { 1157 case kRGBA_8888_GrPixelConfig: 1158 if (unpremul) { 1159 *config8888 = SkCanvas::kRGBA_Unpremul_Config8888; 1160 } else { 1161 *config8888 = SkCanvas::kRGBA_Premul_Config8888; 1162 } 1163 return true; 1164 case kBGRA_8888_GrPixelConfig: 1165 if (unpremul) { 1166 *config8888 = SkCanvas::kBGRA_Unpremul_Config8888; 1167 } else { 1168 *config8888 = SkCanvas::kBGRA_Premul_Config8888; 1169 } 1170 return true; 1171 default: 1172 return false; 1173 } 1174} 1175 1176// It returns a configuration with where the byte position of the R & B components are swapped in 1177// relation to the input config. This should only be called with the result of 1178// grconfig_to_config8888 as it will fail for other configs. 1179SkCanvas::Config8888 swap_config8888_red_and_blue(SkCanvas::Config8888 config8888) { 1180 switch (config8888) { 1181 case SkCanvas::kBGRA_Premul_Config8888: 1182 return SkCanvas::kRGBA_Premul_Config8888; 1183 case SkCanvas::kBGRA_Unpremul_Config8888: 1184 return SkCanvas::kRGBA_Unpremul_Config8888; 1185 case SkCanvas::kRGBA_Premul_Config8888: 1186 return SkCanvas::kBGRA_Premul_Config8888; 1187 case SkCanvas::kRGBA_Unpremul_Config8888: 1188 return SkCanvas::kBGRA_Unpremul_Config8888; 1189 default: 1190 GrCrash("Unexpected input"); 1191 return SkCanvas::kBGRA_Unpremul_Config8888;; 1192 } 1193} 1194} 1195 1196bool GrContext::readRenderTargetPixels(GrRenderTarget* target, 1197 int left, int top, int width, int height, 1198 GrPixelConfig dstConfig, void* buffer, size_t rowBytes, 1199 uint32_t flags) { 1200 SK_TRACE_EVENT0("GrContext::readRenderTargetPixels"); 1201 ASSERT_OWNED_RESOURCE(target); 1202 1203 if (NULL == target) { 1204 target = fDrawState->getRenderTarget(); 1205 if (NULL == target) { 1206 return false; 1207 } 1208 } 1209 1210 if (!(kDontFlush_PixelOpsFlag & flags)) { 1211 this->flush(); 1212 } 1213 1214 // Determine which conversions have to be applied: flipY, swapRAnd, and/or unpremul. 1215 1216 // If fGpu->readPixels would incur a y-flip cost then we will read the pixels upside down. We'll 1217 // either do the flipY by drawing into a scratch with a matrix or on the cpu after the read. 1218 bool flipY = fGpu->readPixelsWillPayForYFlip(target, left, top, 1219 width, height, dstConfig, 1220 rowBytes); 1221 // We ignore the preferred config if it is different than our config unless it is an R/B swap. 1222 // In that case we'll perform an R and B swap while drawing to a scratch texture of the swapped 1223 // config. Then we will call readPixels on the scratch with the swapped config. The swaps during 1224 // the draw cancels out the fact that we call readPixels with a config that is R/B swapped from 1225 // dstConfig. 1226 GrPixelConfig readConfig = dstConfig; 1227 bool swapRAndB = false; 1228 if (GrPixelConfigSwapRAndB(dstConfig) == fGpu->preferredReadPixelsConfig(dstConfig)) { 1229 readConfig = GrPixelConfigSwapRAndB(readConfig); 1230 swapRAndB = true; 1231 } 1232 1233 bool unpremul = SkToBool(kUnpremul_PixelOpsFlag & flags); 1234 1235 if (unpremul && !GrPixelConfigIs8888(dstConfig)) { 1236 // The unpremul flag is only allowed for these two configs. 1237 return false; 1238 } 1239 1240 // If the src is a texture and we would have to do conversions after read pixels, we instead 1241 // do the conversions by drawing the src to a scratch texture. If we handle any of the 1242 // conversions in the draw we set the corresponding bool to false so that we don't reapply it 1243 // on the read back pixels. 1244 GrTexture* src = target->asTexture(); 1245 GrAutoScratchTexture ast; 1246 if (NULL != src && (swapRAndB || unpremul || flipY)) { 1247 // Make the scratch a render target because we don't have a robust readTexturePixels as of 1248 // yet. It calls this function. 1249 GrTextureDesc desc; 1250 desc.fFlags = kRenderTarget_GrTextureFlagBit; 1251 desc.fWidth = width; 1252 desc.fHeight = height; 1253 desc.fConfig = readConfig; 1254 desc.fOrigin = kTopLeft_GrSurfaceOrigin; 1255 1256 // When a full read back is faster than a partial we could always make the scratch exactly 1257 // match the passed rect. However, if we see many different size rectangles we will trash 1258 // our texture cache and pay the cost of creating and destroying many textures. So, we only 1259 // request an exact match when the caller is reading an entire RT. 1260 ScratchTexMatch match = kApprox_ScratchTexMatch; 1261 if (0 == left && 1262 0 == top && 1263 target->width() == width && 1264 target->height() == height && 1265 fGpu->fullReadPixelsIsFasterThanPartial()) { 1266 match = kExact_ScratchTexMatch; 1267 } 1268 ast.set(this, desc, match); 1269 GrTexture* texture = ast.texture(); 1270 if (texture) { 1271 // compute a matrix to perform the draw 1272 SkMatrix textureMatrix; 1273 textureMatrix.setTranslate(SK_Scalar1 *left, SK_Scalar1 *top); 1274 textureMatrix.postIDiv(src->width(), src->height()); 1275 1276 SkAutoTUnref<const GrEffectRef> effect; 1277 if (unpremul) { 1278 effect.reset(this->createPMToUPMEffect(src, swapRAndB, textureMatrix)); 1279 if (NULL != effect) { 1280 unpremul = false; // we no longer need to do this on CPU after the read back. 1281 } 1282 } 1283 // If we failed to create a PM->UPM effect and have no other conversions to perform then 1284 // there is no longer any point to using the scratch. 1285 if (NULL != effect || flipY || swapRAndB) { 1286 if (!effect) { 1287 effect.reset(GrConfigConversionEffect::Create( 1288 src, 1289 swapRAndB, 1290 GrConfigConversionEffect::kNone_PMConversion, 1291 textureMatrix)); 1292 } 1293 swapRAndB = false; // we will handle the swap in the draw. 1294 1295 // We protect the existing geometry here since it may not be 1296 // clear to the caller that a draw operation (i.e., drawSimpleRect) 1297 // can be invoked in this method 1298 GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit); 1299 GrDrawState* drawState = fGpu->drawState(); 1300 GrAssert(effect); 1301 drawState->setEffect(0, effect); 1302 1303 drawState->setRenderTarget(texture->asRenderTarget()); 1304 GrRect rect = GrRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); 1305 fGpu->drawSimpleRect(rect, NULL); 1306 // we want to read back from the scratch's origin 1307 left = 0; 1308 top = 0; 1309 target = texture->asRenderTarget(); 1310 } 1311 } 1312 } 1313 if (!fGpu->readPixels(target, 1314 left, top, width, height, 1315 readConfig, buffer, rowBytes)) { 1316 return false; 1317 } 1318 // Perform any conversions we weren't able to perform using a scratch texture. 1319 if (unpremul || swapRAndB) { 1320 // These are initialized to suppress a warning 1321 SkCanvas::Config8888 srcC8888 = SkCanvas::kNative_Premul_Config8888; 1322 SkCanvas::Config8888 dstC8888 = SkCanvas::kNative_Premul_Config8888; 1323 1324 SkDEBUGCODE(bool c8888IsValid =) grconfig_to_config8888(dstConfig, false, &srcC8888); 1325 grconfig_to_config8888(dstConfig, unpremul, &dstC8888); 1326 1327 if (swapRAndB) { 1328 GrAssert(c8888IsValid); // we should only do r/b swap on 8888 configs 1329 srcC8888 = swap_config8888_red_and_blue(srcC8888); 1330 } 1331 GrAssert(c8888IsValid); 1332 uint32_t* b32 = reinterpret_cast<uint32_t*>(buffer); 1333 SkConvertConfig8888Pixels(b32, rowBytes, dstC8888, 1334 b32, rowBytes, srcC8888, 1335 width, height); 1336 } 1337 return true; 1338} 1339 1340void GrContext::resolveRenderTarget(GrRenderTarget* target) { 1341 GrAssert(target); 1342 ASSERT_OWNED_RESOURCE(target); 1343 // In the future we may track whether there are any pending draws to this 1344 // target. We don't today so we always perform a flush. We don't promise 1345 // this to our clients, though. 1346 this->flush(); 1347 fGpu->resolveRenderTarget(target); 1348} 1349 1350void GrContext::copyTexture(GrTexture* src, GrRenderTarget* dst, const SkIPoint* topLeft) { 1351 if (NULL == src || NULL == dst) { 1352 return; 1353 } 1354 ASSERT_OWNED_RESOURCE(src); 1355 1356 // Writes pending to the source texture are not tracked, so a flush 1357 // is required to ensure that the copy captures the most recent contents 1358 // of the source texture. See similar behavior in 1359 // GrContext::resolveRenderTarget. 1360 this->flush(); 1361 1362 GrDrawTarget::AutoStateRestore asr(fGpu, GrDrawTarget::kReset_ASRInit); 1363 GrDrawState* drawState = fGpu->drawState(); 1364 drawState->setRenderTarget(dst); 1365 SkMatrix sampleM; 1366 sampleM.setIDiv(src->width(), src->height()); 1367 SkIRect srcRect = SkIRect::MakeWH(dst->width(), dst->height()); 1368 if (NULL != topLeft) { 1369 srcRect.offset(*topLeft); 1370 } 1371 SkIRect srcBounds = SkIRect::MakeWH(src->width(), src->height()); 1372 if (!srcRect.intersect(srcBounds)) { 1373 return; 1374 } 1375 sampleM.preTranslate(SkIntToScalar(srcRect.fLeft), SkIntToScalar(srcRect.fTop)); 1376 drawState->createTextureEffect(0, src, sampleM); 1377 SkRect dstR = SkRect::MakeWH(SkIntToScalar(srcRect.width()), SkIntToScalar(srcRect.height())); 1378 fGpu->drawSimpleRect(dstR, NULL); 1379} 1380 1381bool GrContext::writeRenderTargetPixels(GrRenderTarget* target, 1382 int left, int top, int width, int height, 1383 GrPixelConfig srcConfig, 1384 const void* buffer, 1385 size_t rowBytes, 1386 uint32_t flags) { 1387 SK_TRACE_EVENT0("GrContext::writeRenderTargetPixels"); 1388 ASSERT_OWNED_RESOURCE(target); 1389 1390 if (NULL == target) { 1391 target = fDrawState->getRenderTarget(); 1392 if (NULL == target) { 1393 return false; 1394 } 1395 } 1396 1397 // TODO: when underlying api has a direct way to do this we should use it (e.g. glDrawPixels on 1398 // desktop GL). 1399 1400 // We will always call some form of writeTexturePixels and we will pass our flags on to it. 1401 // Thus, we don't perform a flush here since that call will do it (if the kNoFlush flag isn't 1402 // set.) 1403 1404 // If the RT is also a texture and we don't have to premultiply then take the texture path. 1405 // We expect to be at least as fast or faster since it doesn't use an intermediate texture as 1406 // we do below. 1407 1408#if !GR_MAC_BUILD 1409 // At least some drivers on the Mac get confused when glTexImage2D is called on a texture 1410 // attached to an FBO. The FBO still sees the old image. TODO: determine what OS versions and/or 1411 // HW is affected. 1412 if (NULL != target->asTexture() && !(kUnpremul_PixelOpsFlag & flags) && 1413 fGpu->canWriteTexturePixels(target->asTexture(), srcConfig)) { 1414 return this->writeTexturePixels(target->asTexture(), 1415 left, top, width, height, 1416 srcConfig, buffer, rowBytes, flags); 1417 } 1418#endif 1419 1420 // We ignore the preferred config unless it is a R/B swap of the src config. In that case 1421 // we will upload the original src data to a scratch texture but we will spoof it as the swapped 1422 // config. This scratch will then have R and B swapped. We correct for this by swapping again 1423 // when drawing the scratch to the dst using a conversion effect. 1424 bool swapRAndB = false; 1425 GrPixelConfig writeConfig = srcConfig; 1426 if (fGpu->preferredWritePixelsConfig(srcConfig) == GrPixelConfigSwapRAndB(srcConfig)) { 1427 writeConfig = GrPixelConfigSwapRAndB(srcConfig); 1428 swapRAndB = true; 1429 } 1430 1431 GrTextureDesc desc; 1432 desc.fWidth = width; 1433 desc.fHeight = height; 1434 desc.fConfig = writeConfig; 1435 GrAutoScratchTexture ast(this, desc); 1436 GrTexture* texture = ast.texture(); 1437 if (NULL == texture) { 1438 return false; 1439 } 1440 1441 SkAutoTUnref<const GrEffectRef> effect; 1442 SkMatrix textureMatrix; 1443 textureMatrix.setIDiv(texture->width(), texture->height()); 1444 1445 // allocate a tmp buffer and sw convert the pixels to premul 1446 SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0); 1447 1448 if (kUnpremul_PixelOpsFlag & flags) { 1449 if (!GrPixelConfigIs8888(srcConfig)) { 1450 return false; 1451 } 1452 effect.reset(this->createUPMToPMEffect(texture, swapRAndB, textureMatrix)); 1453 // handle the unpremul step on the CPU if we couldn't create an effect to do it. 1454 if (NULL == effect) { 1455 SkCanvas::Config8888 srcConfig8888, dstConfig8888; 1456 GR_DEBUGCODE(bool success = ) 1457 grconfig_to_config8888(srcConfig, true, &srcConfig8888); 1458 GrAssert(success); 1459 GR_DEBUGCODE(success = ) 1460 grconfig_to_config8888(srcConfig, false, &dstConfig8888); 1461 GrAssert(success); 1462 const uint32_t* src = reinterpret_cast<const uint32_t*>(buffer); 1463 tmpPixels.reset(width * height); 1464 SkConvertConfig8888Pixels(tmpPixels.get(), 4 * width, dstConfig8888, 1465 src, rowBytes, srcConfig8888, 1466 width, height); 1467 buffer = tmpPixels.get(); 1468 rowBytes = 4 * width; 1469 } 1470 } 1471 if (NULL == effect) { 1472 effect.reset(GrConfigConversionEffect::Create(texture, 1473 swapRAndB, 1474 GrConfigConversionEffect::kNone_PMConversion, 1475 textureMatrix)); 1476 } 1477 1478 if (!this->writeTexturePixels(texture, 1479 0, 0, width, height, 1480 writeConfig, buffer, rowBytes, 1481 flags & ~kUnpremul_PixelOpsFlag)) { 1482 return false; 1483 } 1484 1485 // writeRenderTargetPixels can be called in the midst of drawing another 1486 // object (e.g., when uploading a SW path rendering to the gpu while 1487 // drawing a rect) so preserve the current geometry. 1488 GrDrawTarget::AutoGeometryAndStatePush agasp(fGpu, GrDrawTarget::kReset_ASRInit); 1489 GrDrawState* drawState = fGpu->drawState(); 1490 GrAssert(effect); 1491 drawState->setEffect(0, effect); 1492 1493 SkMatrix matrix; 1494 matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top)); 1495 drawState->setViewMatrix(matrix); 1496 drawState->setRenderTarget(target); 1497 1498 fGpu->drawSimpleRect(GrRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)), NULL); 1499 return true; 1500} 1501//////////////////////////////////////////////////////////////////////////////// 1502 1503GrDrawTarget* GrContext::prepareToDraw(const GrPaint* paint, BufferedDraw buffered) { 1504 if (kNo_BufferedDraw == buffered && kYes_BufferedDraw == fLastDrawWasBuffered) { 1505 this->flushDrawBuffer(); 1506 fLastDrawWasBuffered = kNo_BufferedDraw; 1507 } 1508 if (NULL != paint) { 1509 GrAssert(fDrawState->stagesDisabled()); 1510 fDrawState->setFromPaint(*paint); 1511#if GR_DEBUG_PARTIAL_COVERAGE_CHECK 1512 if ((paint->hasMask() || 0xff != paint->fCoverage) && 1513 !fGpu->canApplyCoverage()) { 1514 GrPrintf("Partial pixel coverage will be incorrectly blended.\n"); 1515 } 1516#endif 1517 } 1518 if (kYes_BufferedDraw == buffered) { 1519 fDrawBuffer->setClip(fGpu->getClip()); 1520 fLastDrawWasBuffered = kYes_BufferedDraw; 1521 return fDrawBuffer; 1522 } else { 1523 GrAssert(kNo_BufferedDraw == buffered); 1524 return fGpu; 1525 } 1526} 1527 1528/* 1529 * This method finds a path renderer that can draw the specified path on 1530 * the provided target. 1531 * Due to its expense, the software path renderer has split out so it can 1532 * can be individually allowed/disallowed via the "allowSW" boolean. 1533 */ 1534GrPathRenderer* GrContext::getPathRenderer(const SkPath& path, 1535 const SkStrokeRec& stroke, 1536 const GrDrawTarget* target, 1537 bool allowSW, 1538 GrPathRendererChain::DrawType drawType, 1539 GrPathRendererChain::StencilSupport* stencilSupport) { 1540 1541 if (NULL == fPathRendererChain) { 1542 fPathRendererChain = SkNEW_ARGS(GrPathRendererChain, (this)); 1543 } 1544 1545 GrPathRenderer* pr = fPathRendererChain->getPathRenderer(path, 1546 stroke, 1547 target, 1548 drawType, 1549 stencilSupport); 1550 1551 if (NULL == pr && allowSW) { 1552 if (NULL == fSoftwarePathRenderer) { 1553 fSoftwarePathRenderer = SkNEW_ARGS(GrSoftwarePathRenderer, (this)); 1554 } 1555 pr = fSoftwarePathRenderer; 1556 } 1557 1558 return pr; 1559} 1560 1561//////////////////////////////////////////////////////////////////////////////// 1562 1563void GrContext::setRenderTarget(GrRenderTarget* target) { 1564 ASSERT_OWNED_RESOURCE(target); 1565 fDrawState->setRenderTarget(target); 1566} 1567 1568GrRenderTarget* GrContext::getRenderTarget() { 1569 return fDrawState->getRenderTarget(); 1570} 1571 1572const GrRenderTarget* GrContext::getRenderTarget() const { 1573 return fDrawState->getRenderTarget(); 1574} 1575 1576bool GrContext::isConfigRenderable(GrPixelConfig config) const { 1577 return fGpu->isConfigRenderable(config); 1578} 1579 1580const SkMatrix& GrContext::getMatrix() const { 1581 return fDrawState->getViewMatrix(); 1582} 1583 1584void GrContext::setMatrix(const SkMatrix& m) { 1585 fDrawState->setViewMatrix(m); 1586} 1587 1588void GrContext::setIdentityMatrix() { 1589 fDrawState->viewMatrix()->reset(); 1590} 1591 1592void GrContext::concatMatrix(const SkMatrix& m) const { 1593 fDrawState->preConcatViewMatrix(m); 1594} 1595 1596static inline intptr_t setOrClear(intptr_t bits, int shift, intptr_t pred) { 1597 intptr_t mask = 1 << shift; 1598 if (pred) { 1599 bits |= mask; 1600 } else { 1601 bits &= ~mask; 1602 } 1603 return bits; 1604} 1605 1606void GrContext::setupDrawBuffer() { 1607 1608 GrAssert(NULL == fDrawBuffer); 1609 GrAssert(NULL == fDrawBufferVBAllocPool); 1610 GrAssert(NULL == fDrawBufferIBAllocPool); 1611 1612 fDrawBufferVBAllocPool = 1613 SkNEW_ARGS(GrVertexBufferAllocPool, (fGpu, false, 1614 DRAW_BUFFER_VBPOOL_BUFFER_SIZE, 1615 DRAW_BUFFER_VBPOOL_PREALLOC_BUFFERS)); 1616 fDrawBufferIBAllocPool = 1617 SkNEW_ARGS(GrIndexBufferAllocPool, (fGpu, false, 1618 DRAW_BUFFER_IBPOOL_BUFFER_SIZE, 1619 DRAW_BUFFER_IBPOOL_PREALLOC_BUFFERS)); 1620 1621 fDrawBuffer = SkNEW_ARGS(GrInOrderDrawBuffer, (fGpu, 1622 fDrawBufferVBAllocPool, 1623 fDrawBufferIBAllocPool)); 1624 1625 fDrawBuffer->setDrawState(fDrawState); 1626} 1627 1628GrDrawTarget* GrContext::getTextTarget(const GrPaint& paint) { 1629 return this->prepareToDraw(&paint, BUFFERED_DRAW); 1630} 1631 1632const GrIndexBuffer* GrContext::getQuadIndexBuffer() const { 1633 return fGpu->getQuadIndexBuffer(); 1634} 1635 1636namespace { 1637void test_pm_conversions(GrContext* ctx, int* pmToUPMValue, int* upmToPMValue) { 1638 GrConfigConversionEffect::PMConversion pmToUPM; 1639 GrConfigConversionEffect::PMConversion upmToPM; 1640 GrConfigConversionEffect::TestForPreservingPMConversions(ctx, &pmToUPM, &upmToPM); 1641 *pmToUPMValue = pmToUPM; 1642 *upmToPMValue = upmToPM; 1643} 1644} 1645 1646const GrEffectRef* GrContext::createPMToUPMEffect(GrTexture* texture, 1647 bool swapRAndB, 1648 const SkMatrix& matrix) { 1649 if (!fDidTestPMConversions) { 1650 test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion); 1651 fDidTestPMConversions = true; 1652 } 1653 GrConfigConversionEffect::PMConversion pmToUPM = 1654 static_cast<GrConfigConversionEffect::PMConversion>(fPMToUPMConversion); 1655 if (GrConfigConversionEffect::kNone_PMConversion != pmToUPM) { 1656 return GrConfigConversionEffect::Create(texture, swapRAndB, pmToUPM, matrix); 1657 } else { 1658 return NULL; 1659 } 1660} 1661 1662const GrEffectRef* GrContext::createUPMToPMEffect(GrTexture* texture, 1663 bool swapRAndB, 1664 const SkMatrix& matrix) { 1665 if (!fDidTestPMConversions) { 1666 test_pm_conversions(this, &fPMToUPMConversion, &fUPMToPMConversion); 1667 fDidTestPMConversions = true; 1668 } 1669 GrConfigConversionEffect::PMConversion upmToPM = 1670 static_cast<GrConfigConversionEffect::PMConversion>(fUPMToPMConversion); 1671 if (GrConfigConversionEffect::kNone_PMConversion != upmToPM) { 1672 return GrConfigConversionEffect::Create(texture, swapRAndB, upmToPM, matrix); 1673 } else { 1674 return NULL; 1675 } 1676} 1677 1678GrTexture* GrContext::gaussianBlur(GrTexture* srcTexture, 1679 bool canClobberSrc, 1680 const SkRect& rect, 1681 float sigmaX, float sigmaY) { 1682 ASSERT_OWNED_RESOURCE(srcTexture); 1683 1684 AutoRenderTarget art(this); 1685 1686 AutoMatrix am; 1687 am.setIdentity(this); 1688 1689 SkIRect clearRect; 1690 int scaleFactorX, radiusX; 1691 int scaleFactorY, radiusY; 1692 sigmaX = adjust_sigma(sigmaX, &scaleFactorX, &radiusX); 1693 sigmaY = adjust_sigma(sigmaY, &scaleFactorY, &radiusY); 1694 1695 SkRect srcRect(rect); 1696 scale_rect(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY); 1697 srcRect.roundOut(); 1698 scale_rect(&srcRect, static_cast<float>(scaleFactorX), 1699 static_cast<float>(scaleFactorY)); 1700 1701 AutoClip acs(this, srcRect); 1702 1703 GrAssert(kBGRA_8888_GrPixelConfig == srcTexture->config() || 1704 kRGBA_8888_GrPixelConfig == srcTexture->config() || 1705 kAlpha_8_GrPixelConfig == srcTexture->config()); 1706 1707 GrTextureDesc desc; 1708 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; 1709 desc.fWidth = SkScalarFloorToInt(srcRect.width()); 1710 desc.fHeight = SkScalarFloorToInt(srcRect.height()); 1711 desc.fConfig = srcTexture->config(); 1712 1713 GrAutoScratchTexture temp1, temp2; 1714 GrTexture* dstTexture = temp1.set(this, desc); 1715 GrTexture* tempTexture = canClobberSrc ? srcTexture : temp2.set(this, desc); 1716 if (NULL == dstTexture || NULL == tempTexture) { 1717 return NULL; 1718 } 1719 1720 GrPaint paint; 1721 paint.reset(); 1722 1723 for (int i = 1; i < scaleFactorX || i < scaleFactorY; i *= 2) { 1724 SkMatrix matrix; 1725 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 1726 this->setRenderTarget(dstTexture->asRenderTarget()); 1727 SkRect dstRect(srcRect); 1728 scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f, 1729 i < scaleFactorY ? 0.5f : 1.0f); 1730 1731 paint.colorStage(0)->setEffect(GrSimpleTextureEffect::Create(srcTexture, 1732 matrix, 1733 true))->unref(); 1734 this->drawRectToRect(paint, dstRect, srcRect); 1735 srcRect = dstRect; 1736 srcTexture = dstTexture; 1737 SkTSwap(dstTexture, tempTexture); 1738 } 1739 1740 SkIRect srcIRect; 1741 srcRect.roundOut(&srcIRect); 1742 1743 if (sigmaX > 0.0f) { 1744 if (scaleFactorX > 1) { 1745 // Clear out a radius to the right of the srcRect to prevent the 1746 // X convolution from reading garbage. 1747 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 1748 radiusX, srcIRect.height()); 1749 this->clear(&clearRect, 0x0); 1750 } 1751 1752 this->setRenderTarget(dstTexture->asRenderTarget()); 1753 GrDrawTarget* target = this->prepareToDraw(NULL, BUFFERED_DRAW); 1754 convolve_gaussian(target, srcTexture, srcRect, sigmaX, radiusX, 1755 Gr1DKernelEffect::kX_Direction); 1756 srcTexture = dstTexture; 1757 SkTSwap(dstTexture, tempTexture); 1758 } 1759 1760 if (sigmaY > 0.0f) { 1761 if (scaleFactorY > 1 || sigmaX > 0.0f) { 1762 // Clear out a radius below the srcRect to prevent the Y 1763 // convolution from reading garbage. 1764 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 1765 srcIRect.width(), radiusY); 1766 this->clear(&clearRect, 0x0); 1767 } 1768 1769 this->setRenderTarget(dstTexture->asRenderTarget()); 1770 GrDrawTarget* target = this->prepareToDraw(NULL, BUFFERED_DRAW); 1771 convolve_gaussian(target, srcTexture, srcRect, sigmaY, radiusY, 1772 Gr1DKernelEffect::kY_Direction); 1773 srcTexture = dstTexture; 1774 SkTSwap(dstTexture, tempTexture); 1775 } 1776 1777 if (scaleFactorX > 1 || scaleFactorY > 1) { 1778 // Clear one pixel to the right and below, to accommodate bilinear 1779 // upsampling. 1780 clearRect = SkIRect::MakeXYWH(srcIRect.fLeft, srcIRect.fBottom, 1781 srcIRect.width() + 1, 1); 1782 this->clear(&clearRect, 0x0); 1783 clearRect = SkIRect::MakeXYWH(srcIRect.fRight, srcIRect.fTop, 1784 1, srcIRect.height()); 1785 this->clear(&clearRect, 0x0); 1786 SkMatrix matrix; 1787 // FIXME: This should be mitchell, not bilinear. 1788 matrix.setIDiv(srcTexture->width(), srcTexture->height()); 1789 this->setRenderTarget(dstTexture->asRenderTarget()); 1790 paint.colorStage(0)->setEffect(GrSimpleTextureEffect::Create(srcTexture, 1791 matrix, 1792 true))->unref(); 1793 SkRect dstRect(srcRect); 1794 scale_rect(&dstRect, (float) scaleFactorX, (float) scaleFactorY); 1795 this->drawRectToRect(paint, dstRect, srcRect); 1796 srcRect = dstRect; 1797 srcTexture = dstTexture; 1798 SkTSwap(dstTexture, tempTexture); 1799 } 1800 if (srcTexture == temp1.texture()) { 1801 return temp1.detach(); 1802 } else if (srcTexture == temp2.texture()) { 1803 return temp2.detach(); 1804 } else { 1805 srcTexture->ref(); 1806 return srcTexture; 1807 } 1808} 1809 1810/////////////////////////////////////////////////////////////////////////////// 1811#if GR_CACHE_STATS 1812void GrContext::printCacheStats() const { 1813 fTextureCache->printStats(); 1814} 1815#endif 1816