| /* | |
| Copyright 2010 Google Inc. | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include "GrContext.h" | |
| #include "GrTypes.h" | |
| #include "GrTextureCache.h" | |
| #include "GrTextStrike.h" | |
| #include "GrMemory.h" | |
| #include "GrPathIter.h" | |
| #include "GrClipIterator.h" | |
| #include "GrIndexBuffer.h" | |
| #include "GrInOrderDrawBuffer.h" | |
| #include "GrBufferAllocPool.h" | |
| #define DEFER_TEXT_RENDERING 1 | |
| static const size_t MAX_TEXTURE_CACHE_COUNT = 128; | |
| static const size_t MAX_TEXTURE_CACHE_BYTES = 8 * 1024 * 1024; | |
| static const uint32_t TEXT_POOL_VB_SIZE = 1 << 18; // enough to draw 4K untextured glyphs | |
| static const uint32_t NUM_TEXT_POOL_VBS = 4; | |
| GrContext* GrContext::Create(GrGpu::Engine engine, | |
| GrGpu::Platform3DContext context3D) { | |
| GrContext* ctx = NULL; | |
| GrGpu* fGpu = GrGpu::Create(engine, context3D); | |
| if (NULL != fGpu) { | |
| ctx = new GrContext(fGpu); | |
| fGpu->unref(); | |
| } | |
| return ctx; | |
| } | |
| GrContext* GrContext::CreateGLShaderContext() { | |
| return GrContext::Create(GrGpu::kOpenGL_Shaders_Engine, NULL); | |
| } | |
| GrContext::~GrContext() { | |
| fGpu->unref(); | |
| delete fTextureCache; | |
| delete fFontCache; | |
| delete fTextDrawBuffer; | |
| delete fTextVBAllocPool; | |
| delete fTextIBAllocPool; | |
| } | |
| void GrContext::abandonAllTextures() { | |
| fTextureCache->deleteAll(GrTextureCache::kAbandonTexture_DeleteMode); | |
| fFontCache->abandonAll(); | |
| } | |
| GrTextureEntry* GrContext::findAndLockTexture(GrTextureKey* key, | |
| const GrSamplerState& sampler) { | |
| finalizeTextureKey(key, sampler); | |
| return fTextureCache->findAndLock(*key); | |
| } | |
| static void stretchImage(void* dst, | |
| int dstW, | |
| int dstH, | |
| void* src, | |
| int srcW, | |
| int srcH, | |
| int bpp) { | |
| GrFixed dx = (srcW << 16) / dstW; | |
| GrFixed dy = (srcH << 16) / dstH; | |
| GrFixed y = dy >> 1; | |
| int dstXLimit = dstW*bpp; | |
| for (int j = 0; j < dstH; ++j) { | |
| GrFixed x = dx >> 1; | |
| void* srcRow = (uint8_t*)src + (y>>16)*srcW*bpp; | |
| void* dstRow = (uint8_t*)dst + j*dstW*bpp; | |
| for (int i = 0; i < dstXLimit; i += bpp) { | |
| memcpy((uint8_t*) dstRow + i, | |
| (uint8_t*) srcRow + (x>>16)*bpp, | |
| bpp); | |
| x += dx; | |
| } | |
| y += dy; | |
| } | |
| } | |
| GrTextureEntry* GrContext::createAndLockTexture(GrTextureKey* key, | |
| const GrSamplerState& sampler, | |
| const GrGpu::TextureDesc& desc, | |
| void* srcData, size_t rowBytes) { | |
| GrAssert(key->width() == desc.fWidth); | |
| GrAssert(key->height() == desc.fHeight); | |
| #if GR_DUMP_TEXTURE_UPLOAD | |
| GrPrintf("GrContext::createAndLockTexture [%d %d]\n", desc.fWidth, desc.fHeight); | |
| #endif | |
| GrTextureEntry* entry = NULL; | |
| bool special = finalizeTextureKey(key, sampler); | |
| if (special) { | |
| GrTextureEntry* clampEntry; | |
| GrTextureKey clampKey(*key); | |
| clampEntry = findAndLockTexture(&clampKey, GrSamplerState::ClampNoFilter()); | |
| if (NULL == clampEntry) { | |
| clampEntry = createAndLockTexture(&clampKey, | |
| GrSamplerState::ClampNoFilter(), | |
| desc, srcData, rowBytes); | |
| GrAssert(NULL != clampEntry); | |
| if (NULL == clampEntry) { | |
| return NULL; | |
| } | |
| } | |
| GrTexture* clampTexture = clampEntry->texture(); | |
| GrGpu::TextureDesc rtDesc = desc; | |
| rtDesc.fFlags |= GrGpu::kRenderTarget_TextureFlag | | |
| GrGpu::kNoPathRendering_TextureFlag; | |
| rtDesc.fWidth = GrNextPow2(GrMax<int>(desc.fWidth, | |
| fGpu->minRenderTargetWidth())); | |
| rtDesc.fHeight = GrNextPow2(GrMax<int>(desc.fHeight, | |
| fGpu->minRenderTargetHeight())); | |
| GrTexture* texture = fGpu->createTexture(rtDesc, NULL, 0); | |
| if (NULL != texture) { | |
| GrDrawTarget::AutoStateRestore asr(fGpu); | |
| fGpu->setRenderTarget(texture->asRenderTarget()); | |
| fGpu->setTexture(0, clampEntry->texture()); | |
| fGpu->setStencilPass(GrDrawTarget::kNone_StencilPass); | |
| fGpu->setTextureMatrix(0, GrMatrix::I()); | |
| fGpu->setViewMatrix(GrMatrix::I()); | |
| fGpu->setAlpha(0xff); | |
| fGpu->setBlendFunc(GrDrawTarget::kOne_BlendCoeff, GrDrawTarget::kZero_BlendCoeff); | |
| fGpu->disableState(GrDrawTarget::kDither_StateBit | | |
| GrDrawTarget::kClip_StateBit | | |
| GrDrawTarget::kAntialias_StateBit); | |
| GrSamplerState stretchSampler(GrSamplerState::kClamp_WrapMode, | |
| GrSamplerState::kClamp_WrapMode, | |
| sampler.isFilter()); | |
| fGpu->setSamplerState(0, stretchSampler); | |
| static const GrVertexLayout layout = | |
| GrDrawTarget::StageTexCoordVertexLayoutBit(0,0); | |
| GrDrawTarget::AutoReleaseGeometry arg(fGpu, layout, 4, 0); | |
| if (arg.succeeded()) { | |
| GrPoint* verts = (GrPoint*) arg.vertices(); | |
| verts[0].setIRectFan(0, 0, | |
| texture->contentWidth(), | |
| texture->contentHeight(), | |
| 2*sizeof(GrPoint)); | |
| GrScalar tw = GrFixedToScalar(GR_Fixed1 * | |
| clampTexture->contentWidth() / | |
| clampTexture->allocWidth()); | |
| GrScalar th = GrFixedToScalar(GR_Fixed1 * | |
| clampTexture->contentHeight() / | |
| clampTexture->allocHeight()); | |
| verts[1].setRectFan(0, 0, tw, th, 2*sizeof(GrPoint)); | |
| fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, | |
| 0, 4); | |
| entry = fTextureCache->createAndLock(*key, texture); | |
| } | |
| texture->removeRenderTarget(); | |
| } else { | |
| // TODO: Our CPU stretch doesn't filter. But we create separate | |
| // stretched textures when the sampler state is either filtered or | |
| // not. Either implement filtered stretch blit on CPU or just create | |
| // one when FBO case fails. | |
| rtDesc.fFlags = 0; | |
| // no longer need to clamp at min RT size. | |
| rtDesc.fWidth = GrNextPow2(desc.fWidth); | |
| rtDesc.fHeight = GrNextPow2(desc.fHeight); | |
| int bpp = GrTexture::BytesPerPixel(desc.fFormat); | |
| GrAutoSMalloc<128*128*4> stretchedPixels(bpp * | |
| rtDesc.fWidth * | |
| rtDesc.fHeight); | |
| stretchImage(stretchedPixels.get(), rtDesc.fWidth, rtDesc.fHeight, | |
| srcData, desc.fWidth, desc.fHeight, bpp); | |
| size_t stretchedRowBytes = rtDesc.fWidth * bpp; | |
| GrTexture* texture = fGpu->createTexture(rtDesc, | |
| stretchedPixels.get(), | |
| stretchedRowBytes); | |
| GrAssert(NULL != texture); | |
| entry = fTextureCache->createAndLock(*key, texture); | |
| } | |
| fTextureCache->unlock(clampEntry); | |
| } else { | |
| GrTexture* texture = fGpu->createTexture(desc, srcData, rowBytes); | |
| if (NULL != texture) { | |
| entry = fTextureCache->createAndLock(*key, texture); | |
| } else { | |
| entry = NULL; | |
| } | |
| } | |
| return entry; | |
| } | |
| void GrContext::unlockTexture(GrTextureEntry* entry) { | |
| fTextureCache->unlock(entry); | |
| } | |
| void GrContext::detachCachedTexture(GrTextureEntry* entry) { | |
| fTextureCache->detach(entry); | |
| } | |
| void GrContext::reattachAndUnlockCachedTexture(GrTextureEntry* entry) { | |
| fTextureCache->reattachAndUnlock(entry); | |
| } | |
| GrTexture* GrContext::createUncachedTexture(const GrGpu::TextureDesc& desc, | |
| void* srcData, | |
| size_t rowBytes) { | |
| return fGpu->createTexture(desc, srcData, rowBytes); | |
| } | |
| void GrContext::getTextureCacheLimits(int* maxTextures, | |
| size_t* maxTextureBytes) const { | |
| fTextureCache->getLimits(maxTextures, maxTextureBytes); | |
| } | |
| void GrContext::setTextureCacheLimits(int maxTextures, size_t maxTextureBytes) { | |
| fTextureCache->setLimits(maxTextures, maxTextureBytes); | |
| } | |
| int GrContext::getMaxTextureDimension() { | |
| return fGpu->maxTextureDimension(); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| GrRenderTarget* GrContext::createPlatformRenderTarget(intptr_t platformRenderTarget, | |
| int width, int height) { | |
| return fGpu->createPlatformRenderTarget(platformRenderTarget, | |
| width, height); | |
| } | |
| bool GrContext::supportsIndex8PixelConfig(const GrSamplerState& sampler, | |
| int width, int height) { | |
| if (!fGpu->supports8BitPalette()) { | |
| return false; | |
| } | |
| bool isPow2 = GrIsPow2(width) && GrIsPow2(height); | |
| if (!isPow2) { | |
| if (!fGpu->npotTextureSupport()) { | |
| return false; | |
| } | |
| bool tiled = sampler.getWrapX() != GrSamplerState::kClamp_WrapMode || | |
| sampler.getWrapY() != GrSamplerState::kClamp_WrapMode; | |
| if (tiled && !fGpu->npotTextureTileSupport()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| void GrContext::setClip(const GrClip& clip) { | |
| fGpu->setClip(clip); | |
| fGpu->enableState(GrDrawTarget::kClip_StateBit); | |
| } | |
| void GrContext::setClip(const GrIRect& rect) { | |
| GrClip clip; | |
| clip.setRect(rect); | |
| fGpu->setClip(clip); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| void GrContext::eraseColor(GrColor color) { | |
| fGpu->eraseColor(color); | |
| } | |
| void GrContext::drawPaint(const GrPaint& paint) { | |
| // set rect to be big enough to fill the space, but not super-huge, so we | |
| // don't overflow fixed-point implementations | |
| GrRect r(fGpu->getClip().getBounds()); | |
| GrMatrix inverse; | |
| if (fGpu->getViewInverse(&inverse)) { | |
| inverse.mapRect(&r); | |
| } else { | |
| GrPrintf("---- fGpu->getViewInverse failed\n"); | |
| } | |
| this->drawRect(paint, r); | |
| } | |
| /* create a triangle strip that strokes the specified triangle. There are 8 | |
| unique vertices, but we repreat the last 2 to close up. Alternatively we | |
| could use an indices array, and then only send 8 verts, but not sure that | |
| would be faster. | |
| */ | |
| static void setStrokeRectStrip(GrPoint verts[10], const GrRect& rect, | |
| GrScalar width) { | |
| const GrScalar rad = GrScalarHalf(width); | |
| verts[0].set(rect.fLeft + rad, rect.fTop + rad); | |
| verts[1].set(rect.fLeft - rad, rect.fTop - rad); | |
| verts[2].set(rect.fRight - rad, rect.fTop + rad); | |
| verts[3].set(rect.fRight + rad, rect.fTop - rad); | |
| verts[4].set(rect.fRight - rad, rect.fBottom - rad); | |
| verts[5].set(rect.fRight + rad, rect.fBottom + rad); | |
| verts[6].set(rect.fLeft + rad, rect.fBottom - rad); | |
| verts[7].set(rect.fLeft - rad, rect.fBottom + rad); | |
| verts[8] = verts[0]; | |
| verts[9] = verts[1]; | |
| } | |
| void GrContext::drawRect(const GrPaint& paint, | |
| const GrRect& rect, | |
| GrScalar width, | |
| const GrMatrix* matrix) { | |
| bool textured = NULL != paint.getTexture(); | |
| GrVertexLayout layout = (textured) ? | |
| GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0) : | |
| 0; | |
| this->prepareToDraw(paint); | |
| if (width >= 0) { | |
| // TODO: consider making static vertex buffers for these cases. | |
| // Hairline could be done by just adding closing vertex to | |
| // unitSquareVertexBuffer() | |
| static const int worstCaseVertCount = 10; | |
| GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, worstCaseVertCount, 0); | |
| if (!geo.succeeded()) { | |
| return; | |
| } | |
| GrDrawTarget::PrimitiveType primType; | |
| int vertCount; | |
| GrPoint* vertex = geo.positions(); | |
| if (width > 0) { | |
| vertCount = 10; | |
| primType = GrDrawTarget::kTriangleStrip_PrimitiveType; | |
| setStrokeRectStrip(vertex, rect, width); | |
| } else { | |
| // hairline | |
| vertCount = 5; | |
| primType = GrDrawTarget::kLineStrip_PrimitiveType; | |
| vertex[0].set(rect.fLeft, rect.fTop); | |
| vertex[1].set(rect.fRight, rect.fTop); | |
| vertex[2].set(rect.fRight, rect.fBottom); | |
| vertex[3].set(rect.fLeft, rect.fBottom); | |
| vertex[4].set(rect.fLeft, rect.fTop); | |
| } | |
| GrDrawTarget::AutoViewMatrixRestore avmr; | |
| if (NULL != matrix) { | |
| avmr.set(fGpu); | |
| fGpu->concatViewMatrix(*matrix); | |
| fGpu->concatTextureMatrix(0, *matrix); | |
| } | |
| fGpu->drawNonIndexed(primType, 0, vertCount); | |
| } else { | |
| #if GR_STATIC_RECT_VB | |
| fGpu->setVertexSourceToBuffer(fGpu->unitSquareVertexBuffer(), layout); | |
| GrDrawTarget::AutoViewMatrixRestore avmr(fGpu); | |
| GrMatrix m; | |
| m.setAll(rect.width(), 0, rect.fLeft, | |
| 0, rect.height(), rect.fTop, | |
| 0, 0, GrMatrix::I()[8]); | |
| if (NULL != matrix) { | |
| m.postConcat(*matrix); | |
| } | |
| fGpu->concatViewMatrix(m); | |
| if (textured) { | |
| fGpu->concatTextureMatrix(0, m); | |
| } | |
| #else | |
| GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, 4, 0); | |
| GrPoint* vertex = geo.positions(); | |
| vertex->setRectFan(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); | |
| GrDrawTarget::AutoViewMatrixRestore avmr; | |
| if (NULL != matrix) { | |
| avmr.set(fGpu); | |
| fGpu->concatViewMatrix(*matrix); | |
| fGpu->concatTextureMatrix(0, *matrix); | |
| } | |
| #endif | |
| fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, 0, 4); | |
| } | |
| } | |
| void GrContext::drawRectToRect(const GrPaint& paint, | |
| const GrRect& dstRect, | |
| const GrRect& srcRect, | |
| const GrMatrix* dstMatrix, | |
| const GrMatrix* srcMatrix) { | |
| if (NULL == paint.getTexture()) { | |
| drawRect(paint, dstRect, -1, dstMatrix); | |
| return; | |
| } | |
| this->prepareToDraw(paint); | |
| #if GR_STATIC_RECT_VB | |
| GrVertexLayout layout = GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0); | |
| GrDrawTarget::AutoViewMatrixRestore avmr(fGpu); | |
| GrMatrix m; | |
| m.setAll(dstRect.width(), 0, dstRect.fLeft, | |
| 0, dstRect.height(), dstRect.fTop, | |
| 0, 0, GrMatrix::I()[8]); | |
| if (NULL != dstMatrix) { | |
| m.postConcat(*dstMatrix); | |
| } | |
| fGpu->concatViewMatrix(m); | |
| m.setAll(srcRect.width(), 0, srcRect.fLeft, | |
| 0, srcRect.height(), srcRect.fTop, | |
| 0, 0, GrMatrix::I()[8]); | |
| if (NULL != srcMatrix) { | |
| m.postConcat(*srcMatrix); | |
| } | |
| fGpu->concatTextureMatrix(0, m); | |
| fGpu->setVertexSourceToBuffer(fGpu->unitSquareVertexBuffer(), layout); | |
| #else | |
| GrVertexLayout layout = GrDrawTarget::StageTexCoordVertexLayoutBit(0,0); | |
| GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, 4, 0); | |
| GrPoint* pos = geo.positions(); | |
| GrPoint* tex = pos + 1; | |
| static const size_t stride = 2 * sizeof(GrPoint); | |
| pos[0].setRectFan(dstRect.fLeft, dstRect.fTop, | |
| dstRect.fRight, dstRect.fBottom, | |
| stride); | |
| tex[0].setRectFan(srcRect.fLeft, srcRect.fTop, | |
| srcRect.fRight, srcRect.fBottom, | |
| stride); | |
| GrDrawTarget::AutoViewMatrixRestore avmr; | |
| if (NULL != dstMatrix) { | |
| avmr.set(fGpu); | |
| fGpu->concatViewMatrix(*dstMatrix); | |
| } | |
| if (NULL != srcMatrix) { | |
| fGpu->concatTextureMatrix(0, *srcMatrix); | |
| } | |
| #endif | |
| fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, 0, 4); | |
| } | |
| void GrContext::drawVertices(const GrPaint& paint, | |
| GrDrawTarget::PrimitiveType primitiveType, | |
| int vertexCount, | |
| const GrPoint positions[], | |
| const GrPoint texCoords[], | |
| const GrColor colors[], | |
| const uint16_t indices[], | |
| int indexCount) { | |
| GrVertexLayout layout = 0; | |
| int vertexSize = sizeof(GrPoint); | |
| GrDrawTarget::AutoReleaseGeometry geo; | |
| this->prepareToDraw(paint); | |
| if (NULL != paint.getTexture()) { | |
| if (NULL == texCoords) { | |
| layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0); | |
| } else { | |
| layout |= GrDrawTarget::StageTexCoordVertexLayoutBit(0,0); | |
| vertexSize += sizeof(GrPoint); | |
| } | |
| } | |
| if (NULL != colors) { | |
| layout |= GrDrawTarget::kColor_VertexLayoutBit; | |
| vertexSize += sizeof(GrColor); | |
| } | |
| if (sizeof(GrPoint) != vertexSize) { | |
| if (!geo.set(fGpu, layout, vertexCount, 0)) { | |
| GrPrintf("Failed to get space for vertices!"); | |
| return; | |
| } | |
| int texOffsets[GrDrawTarget::kMaxTexCoords]; | |
| int colorOffset; | |
| int vsize = GrDrawTarget::VertexSizeAndOffsetsByIdx(layout, | |
| texOffsets, | |
| &colorOffset); | |
| void* curVertex = geo.vertices(); | |
| for (int i = 0; i < vertexCount; ++i) { | |
| *((GrPoint*)curVertex) = positions[i]; | |
| if (texOffsets[0] > 0) { | |
| *(GrPoint*)((intptr_t)curVertex + texOffsets[0]) = texCoords[i]; | |
| } | |
| if (colorOffset > 0) { | |
| *(GrColor*)((intptr_t)curVertex + colorOffset) = colors[i]; | |
| } | |
| curVertex = (void*)((intptr_t)curVertex + vsize); | |
| } | |
| } else { | |
| fGpu->setVertexSourceToArray(layout, positions, vertexCount); | |
| } | |
| if (NULL != indices) { | |
| fGpu->setIndexSourceToArray(indices, indexCount); | |
| fGpu->drawIndexed(primitiveType, 0, 0, vertexCount, indexCount); | |
| } else { | |
| fGpu->drawNonIndexed(primitiveType, 0, vertexCount); | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| #define STENCIL_OFF 0 // Always disable stencil (even when needed) | |
| #define EVAL_TOL GR_Scalar1 | |
| static const uint32_t MAX_POINTS_PER_CURVE = 1 << 10; | |
| static uint32_t quadratic_point_count(const GrPoint points[], GrScalar tol) { | |
| GrScalar d = points[1].distanceToLineSegmentBetween(points[0], points[2]); | |
| // TODO: fixed points sqrt | |
| if (d < tol) { | |
| return 1; | |
| } else { | |
| // Each time we subdivide, d should be cut in 4. So we need to | |
| // subdivide x = log4(d/tol) times. x subdivisions creates 2^(x) | |
| // points. | |
| // 2^(log4(x)) = sqrt(x); | |
| d = ceilf(sqrtf(d/tol)); | |
| return GrMin(GrNextPow2((uint32_t)d), MAX_POINTS_PER_CURVE); | |
| } | |
| } | |
| static uint32_t generate_quadratic_points(const GrPoint& p0, | |
| const GrPoint& p1, | |
| const GrPoint& p2, | |
| GrScalar tolSqd, | |
| GrPoint** points, | |
| uint32_t pointsLeft) { | |
| if (pointsLeft < 2 || | |
| (p1.distanceToLineSegmentBetweenSqd(p0, p2)) < tolSqd) { | |
| (*points)[0] = p2; | |
| *points += 1; | |
| return 1; | |
| } | |
| GrPoint q[] = { | |
| GrPoint(GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY)), | |
| GrPoint(GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY)), | |
| }; | |
| GrPoint r(GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY)); | |
| pointsLeft >>= 1; | |
| uint32_t a = generate_quadratic_points(p0, q[0], r, tolSqd, points, pointsLeft); | |
| uint32_t b = generate_quadratic_points(r, q[1], p2, tolSqd, points, pointsLeft); | |
| return a + b; | |
| } | |
| static uint32_t cubic_point_count(const GrPoint points[], GrScalar tol) { | |
| GrScalar d = GrMax(points[1].distanceToLineSegmentBetweenSqd(points[0], points[3]), | |
| points[2].distanceToLineSegmentBetweenSqd(points[0], points[3])); | |
| d = sqrtf(d); | |
| if (d < tol) { | |
| return 1; | |
| } else { | |
| d = ceilf(sqrtf(d/tol)); | |
| return GrMin(GrNextPow2((uint32_t)d), MAX_POINTS_PER_CURVE); | |
| } | |
| } | |
| static uint32_t generate_cubic_points(const GrPoint& p0, | |
| const GrPoint& p1, | |
| const GrPoint& p2, | |
| const GrPoint& p3, | |
| GrScalar tolSqd, | |
| GrPoint** points, | |
| uint32_t pointsLeft) { | |
| if (pointsLeft < 2 || | |
| (p1.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd && | |
| p2.distanceToLineSegmentBetweenSqd(p0, p3) < tolSqd)) { | |
| (*points)[0] = p3; | |
| *points += 1; | |
| return 1; | |
| } | |
| GrPoint q[] = { | |
| GrPoint(GrScalarAve(p0.fX, p1.fX), GrScalarAve(p0.fY, p1.fY)), | |
| GrPoint(GrScalarAve(p1.fX, p2.fX), GrScalarAve(p1.fY, p2.fY)), | |
| GrPoint(GrScalarAve(p2.fX, p3.fX), GrScalarAve(p2.fY, p3.fY)) | |
| }; | |
| GrPoint r[] = { | |
| GrPoint(GrScalarAve(q[0].fX, q[1].fX), GrScalarAve(q[0].fY, q[1].fY)), | |
| GrPoint(GrScalarAve(q[1].fX, q[2].fX), GrScalarAve(q[1].fY, q[2].fY)) | |
| }; | |
| GrPoint s(GrScalarAve(r[0].fX, r[1].fX), GrScalarAve(r[0].fY, r[1].fY)); | |
| pointsLeft >>= 1; | |
| uint32_t a = generate_cubic_points(p0, q[0], r[0], s, tolSqd, points, pointsLeft); | |
| uint32_t b = generate_cubic_points(s, r[1], q[2], p3, tolSqd, points, pointsLeft); | |
| return a + b; | |
| } | |
| static int worst_case_point_count(GrPathIter* path, | |
| int* subpaths, | |
| GrScalar tol) { | |
| int pointCount = 0; | |
| *subpaths = 1; | |
| bool first = true; | |
| GrPathIter::Command cmd; | |
| GrPoint pts[4]; | |
| while ((cmd = path->next(pts)) != GrPathIter::kEnd_Command) { | |
| switch (cmd) { | |
| case GrPathIter::kLine_Command: | |
| pointCount += 1; | |
| break; | |
| case GrPathIter::kQuadratic_Command: | |
| pointCount += quadratic_point_count(pts, tol); | |
| break; | |
| case GrPathIter::kCubic_Command: | |
| pointCount += cubic_point_count(pts, tol); | |
| break; | |
| case GrPathIter::kMove_Command: | |
| pointCount += 1; | |
| if (!first) { | |
| ++(*subpaths); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| first = false; | |
| } | |
| return pointCount; | |
| } | |
| static inline bool single_pass_path(const GrPathIter& path, | |
| GrContext::PathFills fill, | |
| const GrGpu& gpu) { | |
| #if STENCIL_OFF | |
| return true; | |
| #else | |
| if (GrContext::kEvenOdd_PathFill == fill) { | |
| GrPathIter::ConvexHint hint = path.hint(); | |
| return hint == GrPathIter::kConvex_ConvexHint || | |
| hint == GrPathIter::kNonOverlappingConvexPieces_ConvexHint; | |
| } else if (GrContext::kWinding_PathFill == fill) { | |
| GrPathIter::ConvexHint hint = path.hint(); | |
| return hint == GrPathIter::kConvex_ConvexHint || | |
| hint == GrPathIter::kNonOverlappingConvexPieces_ConvexHint || | |
| (hint == GrPathIter::kSameWindingConvexPieces_ConvexHint && | |
| gpu.canDisableBlend() && !gpu.isDitherState()); | |
| } | |
| return false; | |
| #endif | |
| } | |
| void GrContext::drawPath(const GrPaint& paint, | |
| GrPathIter* path, | |
| PathFills fill, | |
| const GrPoint* translate) { | |
| this->prepareToDraw(paint); | |
| GrDrawTarget::AutoStateRestore asr(fGpu); | |
| GrMatrix viewM = fGpu->getViewMatrix(); | |
| // In order to tesselate the path we get a bound on how much the matrix can | |
| // stretch when mapping to screen coordinates. | |
| GrScalar stretch = viewM.getMaxStretch(); | |
| bool useStretch = stretch > 0; | |
| GrScalar tol = EVAL_TOL; | |
| if (!useStretch) { | |
| // TODO: deal with perspective in some better way. | |
| tol /= 10; | |
| } else { | |
| // TODO: fixed point divide | |
| GrScalar sinv = 1 / stretch; | |
| tol = GrMul(tol, sinv); | |
| } | |
| GrScalar tolSqd = GrMul(tol, tol); | |
| int subpathCnt; | |
| int maxPts = worst_case_point_count(path, | |
| &subpathCnt, | |
| tol); | |
| GrVertexLayout layout = 0; | |
| if (NULL != paint.getTexture()) { | |
| layout = GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0); | |
| } | |
| // add 4 to hold the bounding rect | |
| GrDrawTarget::AutoReleaseGeometry arg(fGpu, layout, maxPts + 4, 0); | |
| GrPoint* base = (GrPoint*) arg.vertices(); | |
| GrPoint* vert = base; | |
| GrPoint* subpathBase = base; | |
| GrAutoSTMalloc<8, uint16_t> subpathVertCount(subpathCnt); | |
| path->rewind(); | |
| // TODO: use primitve restart if available rather than multiple draws | |
| GrDrawTarget::PrimitiveType type; | |
| int passCount = 0; | |
| GrDrawTarget::StencilPass passes[3]; | |
| bool reverse = false; | |
| if (kHairLine_PathFill == fill) { | |
| type = GrDrawTarget::kLineStrip_PrimitiveType; | |
| passCount = 1; | |
| passes[0] = GrDrawTarget::kNone_StencilPass; | |
| } else { | |
| type = GrDrawTarget::kTriangleFan_PrimitiveType; | |
| if (single_pass_path(*path, fill, *fGpu)) { | |
| passCount = 1; | |
| passes[0] = GrDrawTarget::kNone_StencilPass; | |
| } else { | |
| switch (fill) { | |
| case kInverseEvenOdd_PathFill: | |
| reverse = true; | |
| // fallthrough | |
| case kEvenOdd_PathFill: | |
| passCount = 2; | |
| passes[0] = GrDrawTarget::kEvenOddStencil_StencilPass; | |
| passes[1] = GrDrawTarget::kEvenOddColor_StencilPass; | |
| break; | |
| case kInverseWinding_PathFill: | |
| reverse = true; | |
| // fallthrough | |
| case kWinding_PathFill: | |
| passes[0] = GrDrawTarget::kWindingStencil1_StencilPass; | |
| if (fGpu->supportsSingleStencilPassWinding()) { | |
| passes[1] = GrDrawTarget::kWindingColor_StencilPass; | |
| passCount = 2; | |
| } else { | |
| passes[1] = GrDrawTarget::kWindingStencil2_StencilPass; | |
| passes[2] = GrDrawTarget::kWindingColor_StencilPass; | |
| passCount = 3; | |
| } | |
| break; | |
| default: | |
| GrAssert(!"Unknown path fill!"); | |
| return; | |
| } | |
| } | |
| } | |
| fGpu->setReverseFill(reverse); | |
| GrPoint pts[4]; | |
| bool first = true; | |
| int subpath = 0; | |
| for (;;) { | |
| GrPathIter::Command cmd = path->next(pts); | |
| switch (cmd) { | |
| case GrPathIter::kMove_Command: | |
| if (!first) { | |
| subpathVertCount[subpath] = vert-subpathBase; | |
| subpathBase = vert; | |
| ++subpath; | |
| } | |
| *vert = pts[0]; | |
| vert++; | |
| break; | |
| case GrPathIter::kLine_Command: | |
| *vert = pts[1]; | |
| vert++; | |
| break; | |
| case GrPathIter::kQuadratic_Command: { | |
| generate_quadratic_points(pts[0], pts[1], pts[2], | |
| tolSqd, &vert, | |
| quadratic_point_count(pts, tol)); | |
| break; | |
| } | |
| case GrPathIter::kCubic_Command: { | |
| generate_cubic_points(pts[0], pts[1], pts[2], pts[3], | |
| tolSqd, &vert, | |
| cubic_point_count(pts, tol)); | |
| break; | |
| } | |
| case GrPathIter::kClose_Command: | |
| break; | |
| case GrPathIter::kEnd_Command: | |
| subpathVertCount[subpath] = vert-subpathBase; | |
| ++subpath; // this could be only in debug | |
| goto FINISHED; | |
| } | |
| first = false; | |
| } | |
| FINISHED: | |
| GrAssert(subpath == subpathCnt); | |
| GrAssert((vert - base) <= maxPts); | |
| if (translate) { | |
| int count = vert - base; | |
| for (int i = 0; i < count; i++) { | |
| base[i].offset(translate->fX, translate->fY); | |
| } | |
| } | |
| // arbitrary path complexity cutoff | |
| bool useBounds = fill != kHairLine_PathFill && | |
| (reverse || (vert - base) > 8); | |
| GrPoint* boundsVerts = base + maxPts; | |
| if (useBounds) { | |
| GrRect bounds; | |
| if (reverse) { | |
| GrAssert(NULL != fGpu->getRenderTarget()); | |
| // draw over the whole world. | |
| bounds.setLTRB(0, 0, | |
| GrIntToScalar(fGpu->getRenderTarget()->width()), | |
| GrIntToScalar(fGpu->getRenderTarget()->height())); | |
| GrMatrix vmi; | |
| if (fGpu->getViewInverse(&vmi)) { | |
| vmi.mapRect(&bounds); | |
| } | |
| } else { | |
| bounds.setBounds((GrPoint*)base, vert - base); | |
| } | |
| boundsVerts[0].setRectFan(bounds.fLeft, bounds.fTop, bounds.fRight, | |
| bounds.fBottom); | |
| } | |
| for (int p = 0; p < passCount; ++p) { | |
| fGpu->setStencilPass(passes[p]); | |
| if (useBounds && (GrDrawTarget::kEvenOddColor_StencilPass == passes[p] || | |
| GrDrawTarget::kWindingColor_StencilPass == passes[p])) { | |
| fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, | |
| maxPts, 4); | |
| } else { | |
| int baseVertex = 0; | |
| for (int sp = 0; sp < subpathCnt; ++sp) { | |
| fGpu->drawNonIndexed(type, | |
| baseVertex, | |
| subpathVertCount[sp]); | |
| baseVertex += subpathVertCount[sp]; | |
| } | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| void GrContext::flush(bool flushRenderTarget) { | |
| flushText(); | |
| if (flushRenderTarget) { | |
| fGpu->forceRenderTargetFlush(); | |
| } | |
| } | |
| void GrContext::flushText() { | |
| if (NULL != fTextDrawBuffer) { | |
| fTextDrawBuffer->playback(fGpu); | |
| fTextDrawBuffer->reset(); | |
| } | |
| } | |
| bool GrContext::readPixels(int left, int top, int width, int height, | |
| GrTexture::PixelConfig config, void* buffer) { | |
| this->flush(true); | |
| return fGpu->readPixels(left, top, width, height, config, buffer); | |
| } | |
| void GrContext::writePixels(int left, int top, int width, int height, | |
| GrTexture::PixelConfig config, const void* buffer, | |
| size_t stride) { | |
| // TODO: when underlying api has a direct way to do this we should use it | |
| // (e.g. glDrawPixels on desktop GL). | |
| const GrGpu::TextureDesc desc = { | |
| 0, GrGpu::kNone_AALevel, width, height, config | |
| }; | |
| GrTexture* texture = fGpu->createTexture(desc, buffer, stride); | |
| if (NULL == texture) { | |
| return; | |
| } | |
| this->flush(true); | |
| GrAutoUnref aur(texture); | |
| GrDrawTarget::AutoStateRestore asr(fGpu); | |
| GrMatrix matrix; | |
| matrix.setTranslate(GrIntToScalar(left), GrIntToScalar(top)); | |
| fGpu->setViewMatrix(matrix); | |
| matrix.setScale(GR_Scalar1 / texture->allocWidth(), | |
| GR_Scalar1 / texture->allocHeight()); | |
| fGpu->setTextureMatrix(0, matrix); | |
| fGpu->disableState(GrDrawTarget::kClip_StateBit); | |
| fGpu->setAlpha(0xFF); | |
| fGpu->setBlendFunc(GrDrawTarget::kOne_BlendCoeff, | |
| GrDrawTarget::kZero_BlendCoeff); | |
| fGpu->setTexture(0, texture); | |
| fGpu->setSamplerState(0, GrSamplerState::ClampNoFilter()); | |
| GrVertexLayout layout = GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(0); | |
| static const int VCOUNT = 4; | |
| GrDrawTarget::AutoReleaseGeometry geo(fGpu, layout, VCOUNT, 0); | |
| if (!geo.succeeded()) { | |
| return; | |
| } | |
| ((GrPoint*)geo.vertices())->setIRectFan(0, 0, width, height); | |
| fGpu->drawNonIndexed(GrDrawTarget::kTriangleFan_PrimitiveType, 0, VCOUNT); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| void GrContext::SetPaint(const GrPaint& paint, GrDrawTarget* target) { | |
| target->setTexture(0, paint.getTexture()); | |
| target->setTextureMatrix(0, paint.fTextureMatrix); | |
| target->setSamplerState(0, paint.fSampler); | |
| target->setColor(paint.fColor); | |
| if (paint.fDither) { | |
| target->enableState(GrDrawTarget::kDither_StateBit); | |
| } else { | |
| target->disableState(GrDrawTarget::kDither_StateBit); | |
| } | |
| if (paint.fAntiAlias) { | |
| target->enableState(GrDrawTarget::kAntialias_StateBit); | |
| } else { | |
| target->disableState(GrDrawTarget::kAntialias_StateBit); | |
| } | |
| target->setBlendFunc(paint.fSrcBlendCoeff, paint.fDstBlendCoeff); | |
| } | |
| void GrContext::prepareToDraw(const GrPaint& paint) { | |
| flushText(); | |
| SetPaint(paint, fGpu); | |
| } | |
| //////////////////////////////////////////////////////////////////////////////// | |
| void GrContext::resetContext() { | |
| fGpu->resetContext(); | |
| } | |
| void GrContext::setRenderTarget(GrRenderTarget* target) { | |
| flushText(); | |
| fGpu->setRenderTarget(target); | |
| } | |
| GrRenderTarget* GrContext::getRenderTarget() { | |
| return fGpu->getRenderTarget(); | |
| } | |
| const GrRenderTarget* GrContext::getRenderTarget() const { | |
| return fGpu->getRenderTarget(); | |
| } | |
| const GrMatrix& GrContext::getMatrix() const { | |
| return fGpu->getViewMatrix(); | |
| } | |
| void GrContext::setMatrix(const GrMatrix& m) { | |
| fGpu->setViewMatrix(m); | |
| } | |
| void GrContext::concatMatrix(const GrMatrix& m) const { | |
| fGpu->concatViewMatrix(m); | |
| } | |
| static inline intptr_t setOrClear(intptr_t bits, int shift, intptr_t pred) { | |
| intptr_t mask = 1 << shift; | |
| if (pred) { | |
| bits |= mask; | |
| } else { | |
| bits &= ~mask; | |
| } | |
| return bits; | |
| } | |
| void GrContext::resetStats() { | |
| fGpu->resetStats(); | |
| } | |
| const GrGpu::Stats& GrContext::getStats() const { | |
| return fGpu->getStats(); | |
| } | |
| void GrContext::printStats() const { | |
| fGpu->printStats(); | |
| } | |
| GrContext::GrContext(GrGpu* gpu) { | |
| fGpu = gpu; | |
| fGpu->ref(); | |
| fTextureCache = new GrTextureCache(MAX_TEXTURE_CACHE_COUNT, | |
| MAX_TEXTURE_CACHE_BYTES); | |
| fFontCache = new GrFontCache(fGpu); | |
| #if DEFER_TEXT_RENDERING | |
| fTextVBAllocPool = new GrVertexBufferAllocPool(gpu, | |
| false, | |
| TEXT_POOL_VB_SIZE, | |
| NUM_TEXT_POOL_VBS); | |
| fTextIBAllocPool = new GrIndexBufferAllocPool(gpu, false, 0, 0); | |
| fTextDrawBuffer = new GrInOrderDrawBuffer(fTextVBAllocPool, | |
| fTextIBAllocPool); | |
| #else | |
| fTextDrawBuffer = NULL; | |
| fTextVBAllocPool = NULL; | |
| fTextIBAllocPool = NULL; | |
| #endif | |
| } | |
| bool GrContext::finalizeTextureKey(GrTextureKey* key, | |
| const GrSamplerState& sampler) const { | |
| uint32_t bits = 0; | |
| uint16_t width = key->width(); | |
| uint16_t height = key->height(); | |
| if (!fGpu->npotTextureTileSupport()) { | |
| bool isPow2 = GrIsPow2(width) && GrIsPow2(height); | |
| bool tiled = (sampler.getWrapX() != GrSamplerState::kClamp_WrapMode) || | |
| (sampler.getWrapY() != GrSamplerState::kClamp_WrapMode); | |
| if (tiled && !isPow2) { | |
| bits |= 1; | |
| bits |= sampler.isFilter() ? 2 : 0; | |
| } | |
| } | |
| key->finalize(bits); | |
| return 0 != bits; | |
| } | |
| GrDrawTarget* GrContext::getTextTarget(const GrPaint& paint) { | |
| GrDrawTarget* target; | |
| #if DEFER_TEXT_RENDERING | |
| fTextDrawBuffer->initializeDrawStateAndClip(*fGpu); | |
| target = fTextDrawBuffer; | |
| #else | |
| target = fGpu; | |
| #endif | |
| SetPaint(paint, target); | |
| return target; | |
| } | |
| const GrIndexBuffer* GrContext::quadIndexBuffer() const { | |
| return fGpu->quadIndexBuffer(); | |
| } | |
| int GrContext::maxQuadsInIndexBuffer() const { | |
| return fGpu->maxQuadsInIndexBuffer(); | |
| } | |