diff options
author | joshualitt <joshualitt@chromium.org> | 2015-11-20 13:37:32 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-20 13:37:32 -0800 |
commit | a751c97e30f988b8d832be6b552a6085d484412d (patch) | |
tree | 632d4681454a0688c1d77c01f84c11aceba2aa5a | |
parent | 897c9937636c2287bb217c76da9a56afb43050ac (diff) |
Factor out GrAtlasTextBatch fromt GrAtlasTextContext
BUG=skia:
Review URL: https://codereview.chromium.org/1458233003
-rw-r--r-- | gyp/gpu.gypi | 2 | ||||
-rw-r--r-- | src/gpu/GrAtlasTextContext.cpp | 719 | ||||
-rw-r--r-- | src/gpu/GrAtlasTextContext.h | 3 | ||||
-rw-r--r-- | src/gpu/GrTextContext.h | 2 | ||||
-rw-r--r-- | src/gpu/batches/GrAtlasTextBatch.cpp | 507 | ||||
-rw-r--r-- | src/gpu/batches/GrAtlasTextBatch.h | 227 |
6 files changed, 757 insertions, 703 deletions
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi index f93e935e03..9e5838ecc2 100644 --- a/gyp/gpu.gypi +++ b/gyp/gpu.gypi @@ -210,6 +210,8 @@ '<(skia_src_path)/gpu/batches/GrAAFillRectBatch.h', '<(skia_src_path)/gpu/batches/GrAAStrokeRectBatch.cpp', '<(skia_src_path)/gpu/batches/GrAAStrokeRectBatch.h', + '<(skia_src_path)/gpu/batches/GrAtlasTextBatch.cpp', + '<(skia_src_path)/gpu/batches/GrAtlasTextBatch.h', '<(skia_src_path)/gpu/batches/GrBatch.cpp', '<(skia_src_path)/gpu/batches/GrBatch.h', '<(skia_src_path)/gpu/batches/GrClearBatch.h', diff --git a/src/gpu/GrAtlasTextContext.cpp b/src/gpu/GrAtlasTextContext.cpp index 09190c495b..bb6a6ce90e 100644 --- a/src/gpu/GrAtlasTextContext.cpp +++ b/src/gpu/GrAtlasTextContext.cpp @@ -6,15 +6,10 @@ */ #include "GrAtlasTextContext.h" -#include "GrBatchFontCache.h" -#include "GrBatchFlushState.h" -#include "GrBatchTest.h" #include "GrBlurUtils.h" -#include "GrDefaultGeoProcFactory.h" #include "GrDrawContext.h" #include "GrDrawTarget.h" #include "GrFontScaler.h" -#include "GrResourceProvider.h" #include "GrStrokeInfo.h" #include "GrTextBlobCache.h" #include "GrTexturePriv.h" @@ -37,19 +32,9 @@ #include "SkTextBlob.h" #include "SkTextMapStateProc.h" -#include "batches/GrVertexBatch.h" - -#include "effects/GrBitmapTextGeoProc.h" -#include "effects/GrDistanceFieldGeoProc.h" +#include "batches/GrAtlasTextBatch.h" namespace { -static const size_t kLCDTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16); - -// position + local coord -static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16); - -static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16); - static const int kMinDFFontSize = 18; static const int kSmallDFFontSize = 32; static const int kSmallDFFontLimit = 32; @@ -63,38 +48,6 @@ static const int kLargeDFFontLimit = 2 * kLargeDFFontSize; #endif SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;) -static const int kDistanceAdjustLumShift = 5; - -static const int kVerticesPerGlyph = 4; -static const int kIndicesPerGlyph = 6; - -static size_t get_vertex_stride(GrMaskFormat maskFormat) { - switch (maskFormat) { - case kA8_GrMaskFormat: - return kGrayTextVASize; - case kARGB_GrMaskFormat: - return kColorTextVASize; - default: - return kLCDTextVASize; - } -} - -static size_t get_vertex_stride_df(GrMaskFormat maskFormat, bool useLCDText) { - SkASSERT(maskFormat == kA8_GrMaskFormat); - if (useLCDText) { - return kLCDTextVASize; - } else { - return kGrayTextVASize; - } -} - -static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) { - unsigned r = SkColorGetR(c); - unsigned g = SkColorGetG(c); - unsigned b = SkColorGetB(c); - return GrColorPackRGBA(r, g, b, 0xff); -} - }; GrAtlasTextContext::GrAtlasTextContext(GrContext* context, const SkSurfaceProps& surfaceProps) @@ -102,7 +55,8 @@ GrAtlasTextContext::GrAtlasTextContext(GrContext* context, const SkSurfaceProps& , fDistanceAdjustTable(new DistanceAdjustTable) { // We overallocate vertices in our textblobs based on the assumption that A8 has the greatest // vertexStride - static_assert(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize, + static_assert(GrAtlasTextBatch::kGrayTextVASize >= GrAtlasTextBatch::kColorTextVASize && + GrAtlasTextBatch::kGrayTextVASize >= GrAtlasTextBatch::kLCDTextVASize, "vertex_attribute_changed"); fCurrStrike = nullptr; fCache = context->getTextBlobCache(); @@ -402,7 +356,7 @@ void GrAtlasTextContext::drawTextBlob(GrDrawContext* dc, GrRenderTarget* rt, // but we'd have to clear the subrun information fCache->remove(cacheBlob); cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint, - kGrayTextVASize))); + GrAtlasTextBatch::kGrayTextVASize))); this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y, drawFilter, clipRect, rt, clip); } else { @@ -430,9 +384,9 @@ void GrAtlasTextContext::drawTextBlob(GrDrawContext* dc, GrRenderTarget* rt, } else { if (canCache) { cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, skPaint, - kGrayTextVASize))); + GrAtlasTextBatch::kGrayTextVASize))); } else { - cacheBlob.reset(fCache->createBlob(blob, kGrayTextVASize)); + cacheBlob.reset(fCache->createBlob(blob, GrAtlasTextBatch::kGrayTextVASize)); } this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMatrix, blob, x, y, drawFilter, clipRect, rt, clip); @@ -689,7 +643,7 @@ inline GrAtlasTextBlob* GrAtlasTextContext::setupDFBlob(int glyphCount, const SkPaint& origPaint, const SkMatrix& viewMatrix, SkPaint* dfPaint, SkScalar* textRatio) { - GrAtlasTextBlob* blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize); + GrAtlasTextBlob* blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBatch::kGrayTextVASize); *dfPaint = origPaint; this->initDistanceFieldPaint(blob, dfPaint, textRatio, viewMatrix); @@ -729,7 +683,7 @@ GrAtlasTextContext::createDrawTextBlob(GrRenderTarget* rt, const GrClip& clip, fallbackTxt, fallbackPos, 2, offset, clipRect); } } else { - blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize); + blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBatch::kGrayTextVASize); blob->fViewMatrix = viewMatrix; SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false); @@ -769,7 +723,7 @@ GrAtlasTextContext::createDrawPosTextBlob(GrRenderTarget* rt, const GrClip& clip clipRect); } } else { - blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize); + blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBatch::kGrayTextVASize); blob->fViewMatrix = viewMatrix; SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false); this->internalDrawBMPPosText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text, @@ -1090,7 +1044,7 @@ void GrAtlasTextContext::bmpAppendGlyph(GrAtlasTextBlob* blob, int runIndex, run.fInitialized = true; - size_t vertexStride = get_vertex_stride(format); + size_t vertexStride = GrAtlasTextBatch::GetVertexStride(format); SkRect r; r.fLeft = SkIntToScalar(x); @@ -1168,7 +1122,8 @@ bool GrAtlasTextContext::dfAppendGlyph(GrAtlasTextBlob* blob, int runIndex, SkASSERT(glyph->fMaskFormat == kA8_GrMaskFormat); subRun->fMaskFormat = kA8_GrMaskFormat; - size_t vertexStride = get_vertex_stride_df(kA8_GrMaskFormat, subRun->fUseLCDText); + size_t vertexStride = GrAtlasTextBatch::GetVertexStrideDf(kA8_GrMaskFormat, + subRun->fUseLCDText); bool useColorVerts = !subRun->fUseLCDText; this->appendGlyphCommon(blob, &run, subRun, glyphRect, color, vertexStride, useColorVerts, @@ -1251,645 +1206,9 @@ inline void GrAtlasTextContext::appendGlyphCommon(GrAtlasTextBlob* blob, Run* ru } subRun->fGlyphEndIndex++; - subRun->fVertexEndIndex += vertexStride * kVerticesPerGlyph; + subRun->fVertexEndIndex += vertexStride * GrAtlasTextBatch::kVerticesPerGlyph; } -class TextBatch : public GrVertexBatch { -public: - DEFINE_BATCH_CLASS_ID - - typedef GrAtlasTextContext::DistanceAdjustTable DistanceAdjustTable; - typedef GrAtlasTextBlob Blob; - typedef Blob::Run Run; - typedef Run::SubRunInfo TextInfo; - struct Geometry { - Blob* fBlob; - int fRun; - int fSubRun; - GrColor fColor; - SkScalar fTransX; - SkScalar fTransY; - }; - - static TextBatch* CreateBitmap(GrMaskFormat maskFormat, int glyphCount, - GrBatchFontCache* fontCache) { - TextBatch* batch = new TextBatch; - - batch->fFontCache = fontCache; - switch (maskFormat) { - case kA8_GrMaskFormat: - batch->fMaskType = kGrayscaleCoverageMask_MaskType; - break; - case kA565_GrMaskFormat: - batch->fMaskType = kLCDCoverageMask_MaskType; - break; - case kARGB_GrMaskFormat: - batch->fMaskType = kColorBitmapMask_MaskType; - break; - } - batch->fBatch.fNumGlyphs = glyphCount; - batch->fGeoCount = 1; - batch->fFilteredColor = 0; - batch->fFontCache = fontCache; - batch->fUseBGR = false; - return batch; - } - - static TextBatch* CreateDistanceField(int glyphCount, GrBatchFontCache* fontCache, - const DistanceAdjustTable* distanceAdjustTable, - SkColor filteredColor, bool isLCD, - bool useBGR) { - TextBatch* batch = new TextBatch; - - batch->fFontCache = fontCache; - batch->fMaskType = isLCD ? kLCDDistanceField_MaskType : kGrayscaleDistanceField_MaskType; - batch->fDistanceAdjustTable.reset(SkRef(distanceAdjustTable)); - batch->fFilteredColor = filteredColor; - batch->fUseBGR = useBGR; - batch->fBatch.fNumGlyphs = glyphCount; - batch->fGeoCount = 1; - return batch; - } - - // to avoid even the initial copy of the struct, we have a getter for the first item which - // is used to seed the batch with its initial geometry. After seeding, the client should call - // init() so the Batch can initialize itself - Geometry& geometry() { return fGeoData[0]; } - - void init() { - const Geometry& geo = fGeoData[0]; - fBatch.fColor = geo.fColor; - fBatch.fViewMatrix = geo.fBlob->fViewMatrix; - - // We don't yet position distance field text on the cpu, so we have to map the vertex bounds - // into device space - const Run& run = geo.fBlob->fRuns[geo.fRun]; - if (run.fSubRunInfo[geo.fSubRun].fDrawAsDistanceFields) { - SkRect bounds = run.fVertexBounds; - fBatch.fViewMatrix.mapRect(&bounds); - this->setBounds(bounds); - } else { - this->setBounds(run.fVertexBounds); - } - } - - const char* name() const override { return "TextBatch"; } - - SkString dumpInfo() const override { - SkString str; - - for (int i = 0; i < fGeoCount; ++i) { - str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n", - i, - fGeoData[i].fColor, - fGeoData[i].fTransX, - fGeoData[i].fTransY, - fGeoData[i].fBlob->fRunCount); - } - - str.append(INHERITED::dumpInfo()); - return str; - } - - void getInvariantOutputColor(GrInitInvariantOutput* out) const override { - if (kColorBitmapMask_MaskType == fMaskType) { - out->setUnknownFourComponents(); - } else { - out->setKnownFourComponents(fBatch.fColor); - } - } - - void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override { - switch (fMaskType) { - case kGrayscaleDistanceField_MaskType: - case kGrayscaleCoverageMask_MaskType: - out->setUnknownSingleComponent(); - break; - case kLCDCoverageMask_MaskType: - case kLCDDistanceField_MaskType: - out->setUnknownOpaqueFourComponents(); - out->setUsingLCDCoverage(); - break; - case kColorBitmapMask_MaskType: - out->setKnownSingleComponent(0xff); - } - } - -private: - void initBatchTracker(const GrPipelineOptimizations& opt) override { - // Handle any color overrides - if (!opt.readsColor()) { - fGeoData[0].fColor = GrColor_ILLEGAL; - } - opt.getOverrideColorIfSet(&fGeoData[0].fColor); - - // setup batch properties - fBatch.fColorIgnored = !opt.readsColor(); - fBatch.fColor = fGeoData[0].fColor; - fBatch.fUsesLocalCoords = opt.readsLocalCoords(); - fBatch.fCoverageIgnored = !opt.readsCoverage(); - } - - struct FlushInfo { - SkAutoTUnref<const GrVertexBuffer> fVertexBuffer; - SkAutoTUnref<const GrIndexBuffer> fIndexBuffer; - int fGlyphsToFlush; - int fVertexOffset; - }; - - void onPrepareDraws(Target* target) override { - // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. - // TODO actually only invert if we don't have RGBA - SkMatrix localMatrix; - if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) { - SkDebugf("Cannot invert viewmatrix\n"); - return; - } - - GrTexture* texture = fFontCache->getTexture(this->maskFormat()); - if (!texture) { - SkDebugf("Could not allocate backing texture for atlas\n"); - return; - } - - bool usesDistanceFields = this->usesDistanceFields(); - GrMaskFormat maskFormat = this->maskFormat(); - bool isLCD = this->isLCD(); - - SkAutoTUnref<const GrGeometryProcessor> gp; - if (usesDistanceFields) { - gp.reset(this->setupDfProcessor(this->viewMatrix(), fFilteredColor, this->color(), - texture)); - } else { - GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode); - gp.reset(GrBitmapTextGeoProc::Create(this->color(), - texture, - params, - maskFormat, - localMatrix, - this->usesLocalCoords())); - } - - FlushInfo flushInfo; - flushInfo.fGlyphsToFlush = 0; - size_t vertexStride = gp->getVertexStride(); - SkASSERT(vertexStride == (usesDistanceFields ? - get_vertex_stride_df(maskFormat, isLCD) : - get_vertex_stride(maskFormat))); - - target->initDraw(gp, this->pipeline()); - - int glyphCount = this->numGlyphs(); - const GrVertexBuffer* vertexBuffer; - - void* vertices = target->makeVertexSpace(vertexStride, - glyphCount * kVerticesPerGlyph, - &vertexBuffer, - &flushInfo.fVertexOffset); - flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer)); - flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer()); - if (!vertices || !flushInfo.fVertexBuffer) { - SkDebugf("Could not allocate vertices\n"); - return; - } - - unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices); - - // We cache some values to avoid going to the glyphcache for the same fontScaler twice - // in a row - const SkDescriptor* desc = nullptr; - SkGlyphCache* cache = nullptr; - GrFontScaler* scaler = nullptr; - SkTypeface* typeface = nullptr; - - for (int i = 0; i < fGeoCount; i++) { - Geometry& args = fGeoData[i]; - Blob* blob = args.fBlob; - Run& run = blob->fRuns[args.fRun]; - TextInfo& info = run.fSubRunInfo[args.fSubRun]; - - uint64_t currentAtlasGen = fFontCache->atlasGeneration(maskFormat); - bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen || - info.fStrike->isAbandoned(); - bool regenerateColors; - if (usesDistanceFields) { - regenerateColors = !isLCD && run.fColor != args.fColor; - } else { - regenerateColors = kA8_GrMaskFormat == maskFormat && run.fColor != args.fColor; - } - bool regeneratePositions = args.fTransX != 0.f || args.fTransY != 0.f; - int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex; - - // We regenerate both texture coords and colors in the blob itself, and update the - // atlas generation. If we don't end up purging any unused plots, we can avoid - // regenerating the coords. We could take a finer grained approach to updating texture - // coords but its not clear if the extra bookkeeping would offset any gains. - // To avoid looping over the glyphs twice, we do one loop and conditionally update color - // or coords as needed. One final note, if we have to break a run for an atlas eviction - // then we can't really trust the atlas has all of the correct data. Atlas evictions - // should be pretty rare, so we just always regenerate in those cases - if (regenerateTextureCoords || regenerateColors || regeneratePositions) { - // first regenerate texture coordinates / colors if need be - bool brokenRun = false; - - // Because the GrBatchFontCache may evict the strike a blob depends on using for - // generating its texture coords, we have to track whether or not the strike has - // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is - // otherwise we have to get the new strike, and use that to get the correct glyphs. - // Because we do not have the packed ids, and thus can't look up our glyphs in the - // new strike, we instead keep our ref to the old strike and use the packed ids from - // it. These ids will still be valid as long as we hold the ref. When we are done - // updating our cache of the GrGlyph*s, we drop our ref on the old strike - bool regenerateGlyphs = false; - GrBatchTextStrike* strike = nullptr; - if (regenerateTextureCoords) { - info.fBulkUseToken.reset(); - - // We can reuse if we have a valid strike and our descriptors / typeface are the - // same. The override descriptor is only for the non distance field text within - // a run - const SkDescriptor* newDesc = (run.fOverrideDescriptor && !usesDistanceFields) ? - run.fOverrideDescriptor->getDesc() : - run.fDescriptor.getDesc(); - if (!cache || !SkTypeface::Equal(typeface, run.fTypeface) || - !(desc->equals(*newDesc))) { - if (cache) { - SkGlyphCache::AttachCache(cache); - } - desc = newDesc; - cache = SkGlyphCache::DetachCache(run.fTypeface, desc); - scaler = GrTextContext::GetGrFontScaler(cache); - strike = info.fStrike; - typeface = run.fTypeface; - } - - if (info.fStrike->isAbandoned()) { - regenerateGlyphs = true; - strike = fFontCache->getStrike(scaler); - } else { - strike = info.fStrike; - } - } - - for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) { - if (regenerateTextureCoords) { - size_t glyphOffset = glyphIdx + info.fGlyphStartIndex; - - GrGlyph* glyph = blob->fGlyphs[glyphOffset]; - GrGlyph::PackedID id = glyph->fPackedID; - const SkGlyph& skGlyph = scaler->grToSkGlyph(id); - if (regenerateGlyphs) { - // Get the id from the old glyph, and use the new strike to lookup - // the glyph. - blob->fGlyphs[glyphOffset] = strike->getGlyph(skGlyph, id, maskFormat, - scaler); - } - glyph = blob->fGlyphs[glyphOffset]; - SkASSERT(glyph); - SkASSERT(id == glyph->fPackedID); - // We want to be able to assert this but cannot for testing purposes. - // once skbug:4143 has landed we can revist this assert - //SkASSERT(glyph->fMaskFormat == this->maskFormat()); - - if (!fFontCache->hasGlyph(glyph) && - !strike->addGlyphToAtlas(target, glyph, scaler, skGlyph, maskFormat)) { - this->flush(target, &flushInfo); - target->initDraw(gp, this->pipeline()); - brokenRun = glyphIdx > 0; - - SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(target, - glyph, - scaler, - skGlyph, - maskFormat); - SkASSERT(success); - } - fFontCache->addGlyphToBulkAndSetUseToken(&info.fBulkUseToken, glyph, - target->currentToken()); - - // Texture coords are the last vertex attribute so we get a pointer to the - // first one and then map with stride in regenerateTextureCoords - intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); - vertex += info.fVertexStartIndex; - vertex += vertexStride * glyphIdx * kVerticesPerGlyph; - vertex += vertexStride - sizeof(SkIPoint16); - - this->regenerateTextureCoords(glyph, vertex, vertexStride); - } - - if (regenerateColors) { - intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); - vertex += info.fVertexStartIndex; - vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint); - this->regenerateColors(vertex, vertexStride, args.fColor); - } - - if (regeneratePositions) { - intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); - vertex += info.fVertexStartIndex; - vertex += vertexStride * glyphIdx * kVerticesPerGlyph; - SkScalar transX = args.fTransX; - SkScalar transY = args.fTransY; - this->regeneratePositions(vertex, vertexStride, transX, transY); - } - flushInfo.fGlyphsToFlush++; - } - - // We my have changed the color so update it here - run.fColor = args.fColor; - if (regenerateTextureCoords) { - if (regenerateGlyphs) { - info.fStrike.reset(SkRef(strike)); - } - info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration : - fFontCache->atlasGeneration(maskFormat); - } - } else { - flushInfo.fGlyphsToFlush += glyphCount; - - // set use tokens for all of the glyphs in our subrun. This is only valid if we - // have a valid atlas generation - fFontCache->setUseTokenBulk(info.fBulkUseToken, target->currentToken(), maskFormat); - } - - // now copy all vertices - size_t byteCount = info.fVertexEndIndex - info.fVertexStartIndex; - memcpy(currVertex, blob->fVertices + info.fVertexStartIndex, byteCount); - - currVertex += byteCount; - } - // Make sure to attach the last cache if applicable - if (cache) { - SkGlyphCache::AttachCache(cache); - } - this->flush(target, &flushInfo); - } - - TextBatch() : INHERITED(ClassID()) {} // initialized in factory functions. - - ~TextBatch() { - for (int i = 0; i < fGeoCount; i++) { - fGeoData[i].fBlob->unref(); - } - } - - GrMaskFormat maskFormat() const { - switch (fMaskType) { - case kLCDCoverageMask_MaskType: - return kA565_GrMaskFormat; - case kColorBitmapMask_MaskType: - return kARGB_GrMaskFormat; - case kGrayscaleCoverageMask_MaskType: - case kGrayscaleDistanceField_MaskType: - case kLCDDistanceField_MaskType: - return kA8_GrMaskFormat; - } - return kA8_GrMaskFormat; // suppress warning - } - - bool usesDistanceFields() const { - return kGrayscaleDistanceField_MaskType == fMaskType || - kLCDDistanceField_MaskType == fMaskType; - } - - bool isLCD() const { - return kLCDCoverageMask_MaskType == fMaskType || - kLCDDistanceField_MaskType == fMaskType; - } - - void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) { - int width = glyph->fBounds.width(); - int height = glyph->fBounds.height(); - - int u0, v0, u1, v1; - if (this->usesDistanceFields()) { - u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset; - v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset; - u1 = u0 + width - 2 * SK_DistanceFieldInset; - v1 = v0 + height - 2 * SK_DistanceFieldInset; - } else { - u0 = glyph->fAtlasLocation.fX; - v0 = glyph->fAtlasLocation.fY; - u1 = u0 + width; - v1 = v0 + height; - } - - SkIPoint16* textureCoords; - // V0 - textureCoords = reinterpret_cast<SkIPoint16*>(vertex); - textureCoords->set(u0, v0); - vertex += vertexStride; - - // V1 - textureCoords = reinterpret_cast<SkIPoint16*>(vertex); - textureCoords->set(u0, v1); - vertex += vertexStride; - - // V2 - textureCoords = reinterpret_cast<SkIPoint16*>(vertex); - textureCoords->set(u1, v1); - vertex += vertexStride; - - // V3 - textureCoords = reinterpret_cast<SkIPoint16*>(vertex); - textureCoords->set(u1, v0); - } - - void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) { - for (int i = 0; i < kVerticesPerGlyph; i++) { - SkColor* vcolor = reinterpret_cast<SkColor*>(vertex); - *vcolor = color; - vertex += vertexStride; - } - } - - void regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX, - SkScalar transY) { - for (int i = 0; i < kVerticesPerGlyph; i++) { - SkPoint* point = reinterpret_cast<SkPoint*>(vertex); - point->fX += transX; - point->fY += transY; - vertex += vertexStride; - } - } - - void flush(GrVertexBatch::Target* target, FlushInfo* flushInfo) { - GrVertices vertices; - int maxGlyphsPerDraw = flushInfo->fIndexBuffer->maxQuads(); - vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer, - flushInfo->fIndexBuffer, flushInfo->fVertexOffset, - kVerticesPerGlyph, kIndicesPerGlyph, flushInfo->fGlyphsToFlush, - maxGlyphsPerDraw); - target->draw(vertices); - flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush; - flushInfo->fGlyphsToFlush = 0; - } - - GrColor color() const { return fBatch.fColor; } - const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; } - bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } - int numGlyphs() const { return fBatch.fNumGlyphs; } - - bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { - TextBatch* that = t->cast<TextBatch>(); - if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(), - that->bounds(), caps)) { - return false; - } - - if (fMaskType != that->fMaskType) { - return false; - } - - if (!this->usesDistanceFields()) { - // TODO we can often batch across LCD text if we have dual source blending and don't - // have to use the blend constant - if (kGrayscaleCoverageMask_MaskType != fMaskType && this->color() != that->color()) { - return false; - } - if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { - return false; - } - } else { - if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { - return false; - } - - if (fFilteredColor != that->fFilteredColor) { - return false; - } - - if (fUseBGR != that->fUseBGR) { - return false; - } - - // TODO see note above - if (kLCDDistanceField_MaskType == fMaskType && this->color() != that->color()) { - return false; - } - } - - fBatch.fNumGlyphs += that->numGlyphs(); - - // Reallocate space for geo data if necessary and then import that's geo data. - int newGeoCount = that->fGeoCount + fGeoCount; - // We assume (and here enforce) that the allocation size is the smallest power of two that - // is greater than or equal to the number of geometries (and at least - // kMinGeometryAllocated). - int newAllocSize = GrNextPow2(newGeoCount); - int currAllocSize = SkTMax<int>(kMinGeometryAllocated, GrNextPow2(fGeoCount)); - - if (newGeoCount > currAllocSize) { - fGeoData.realloc(newAllocSize); - } - - memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry)); - // We steal the ref on the blobs from the other TextBatch and set its count to 0 so that - // it doesn't try to unref them. -#ifdef SK_DEBUG - for (int i = 0; i < that->fGeoCount; ++i) { - that->fGeoData.get()[i].fBlob = (Blob*)0x1; - } -#endif - that->fGeoCount = 0; - fGeoCount = newGeoCount; - - this->joinBounds(that->bounds()); - return true; - } - - // TODO just use class params - // TODO trying to figure out why lcd is so whack - GrGeometryProcessor* setupDfProcessor(const SkMatrix& viewMatrix, SkColor filteredColor, - GrColor color, GrTexture* texture) { - GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); - bool isLCD = this->isLCD(); - // set up any flags - uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; - - // see if we need to create a new effect - if (isLCD) { - flags |= kUseLCD_DistanceFieldEffectFlag; - flags |= viewMatrix.rectStaysRect() ? kRectToRect_DistanceFieldEffectFlag : 0; - flags |= fUseBGR ? kBGR_DistanceFieldEffectFlag : 0; - - GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor); - - float redCorrection = - (*fDistanceAdjustTable)[GrColorUnpackR(colorNoPreMul) >> kDistanceAdjustLumShift]; - float greenCorrection = - (*fDistanceAdjustTable)[GrColorUnpackG(colorNoPreMul) >> kDistanceAdjustLumShift]; - float blueCorrection = - (*fDistanceAdjustTable)[GrColorUnpackB(colorNoPreMul) >> kDistanceAdjustLumShift]; - GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust = - GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(redCorrection, - greenCorrection, - blueCorrection); - - return GrDistanceFieldLCDTextGeoProc::Create(color, - viewMatrix, - texture, - params, - widthAdjust, - flags, - this->usesLocalCoords()); - } else { - flags |= kColorAttr_DistanceFieldEffectFlag; -#ifdef SK_GAMMA_APPLY_TO_A8 - U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, filteredColor); - float correction = (*fDistanceAdjustTable)[lum >> kDistanceAdjustLumShift]; - return GrDistanceFieldA8TextGeoProc::Create(color, - viewMatrix, - texture, - params, - correction, - flags, - this->usesLocalCoords()); -#else - return GrDistanceFieldA8TextGeoProc::Create(color, - viewMatrix, - texture, - params, - flags, - this->usesLocalCoords()); -#endif - } - - } - - struct BatchTracker { - GrColor fColor; - SkMatrix fViewMatrix; - bool fUsesLocalCoords; - bool fColorIgnored; - bool fCoverageIgnored; - int fNumGlyphs; - }; - - BatchTracker fBatch; - // The minimum number of Geometry we will try to allocate. - enum { kMinGeometryAllocated = 4 }; - SkAutoSTMalloc<kMinGeometryAllocated, Geometry> fGeoData; - int fGeoCount; - - enum MaskType { - kGrayscaleCoverageMask_MaskType, - kLCDCoverageMask_MaskType, - kColorBitmapMask_MaskType, - kGrayscaleDistanceField_MaskType, - kLCDDistanceField_MaskType, - } fMaskType; - bool fUseBGR; // fold this into the enum? - - GrBatchFontCache* fFontCache; - - // Distance field properties - SkAutoTUnref<const DistanceAdjustTable> fDistanceAdjustTable; - SkColor fFilteredColor; - - typedef GrVertexBatch INHERITED; -}; - void GrAtlasTextContext::flushRunAsPaths(GrDrawContext* dc, GrRenderTarget* rt, const SkTextBlobRunIterator& it, const GrClip& clip, const SkPaint& skPaint, @@ -1942,7 +1261,7 @@ GrAtlasTextContext::createBatch(GrAtlasTextBlob* cacheBlob, const PerSubRunInfo& subRunColor = color; } - TextBatch* batch; + GrAtlasTextBatch* batch; if (info.fDrawAsDistanceFields) { SkColor filteredColor; SkColorFilter* colorFilter = skPaint.getColorFilter(); @@ -1952,13 +1271,13 @@ GrAtlasTextContext::createBatch(GrAtlasTextBlob* cacheBlob, const PerSubRunInfo& filteredColor = skPaint.getColor(); } bool useBGR = SkPixelGeometryIsBGR(fSurfaceProps.pixelGeometry()); - batch = TextBatch::CreateDistanceField(glyphCount, fContext->getBatchFontCache(), - fDistanceAdjustTable, filteredColor, - info.fUseLCDText, useBGR); + batch = GrAtlasTextBatch::CreateDistanceField(glyphCount, fContext->getBatchFontCache(), + fDistanceAdjustTable, filteredColor, + info.fUseLCDText, useBGR); } else { - batch = TextBatch::CreateBitmap(format, glyphCount, fContext->getBatchFontCache()); + batch = GrAtlasTextBatch::CreateBitmap(format, glyphCount, fContext->getBatchFontCache()); } - TextBatch::Geometry& geometry = batch->geometry(); + GrAtlasTextBatch::Geometry& geometry = batch->geometry(); geometry.fBlob = SkRef(cacheBlob); geometry.fRun = run; geometry.fSubRun = subRun; diff --git a/src/gpu/GrAtlasTextContext.h b/src/gpu/GrAtlasTextContext.h index f5421a669c..81f1ddd378 100644 --- a/src/gpu/GrAtlasTextContext.h +++ b/src/gpu/GrAtlasTextContext.h @@ -27,7 +27,6 @@ class SkGlyph; /* * This class implements GrTextContext using standard bitmap fonts, and can also process textblobs. - * TODO replace GrBitmapTextContext */ class GrAtlasTextContext : public GrTextContext { public: @@ -184,7 +183,7 @@ private: SkAutoTUnref<const DistanceAdjustTable> fDistanceAdjustTable; friend class GrTextBlobCache; - friend class TextBatch; + friend class GrAtlasTextBatch; #ifdef GR_TEST_UTILS DRAW_BATCH_TEST_FRIEND(TextBlobBatch); diff --git a/src/gpu/GrTextContext.h b/src/gpu/GrTextContext.h index c1e8af5496..05e179af53 100644 --- a/src/gpu/GrTextContext.h +++ b/src/gpu/GrTextContext.h @@ -82,7 +82,7 @@ protected: const char text[], size_t byteLength, SkVector* stopVector); static uint32_t FilterTextFlags(const SkSurfaceProps& surfaceProps, const SkPaint& paint); - friend class TextBatch; + friend class GrAtlasTextBatch; }; #endif diff --git a/src/gpu/batches/GrAtlasTextBatch.cpp b/src/gpu/batches/GrAtlasTextBatch.cpp new file mode 100644 index 0000000000..91088c0c59 --- /dev/null +++ b/src/gpu/batches/GrAtlasTextBatch.cpp @@ -0,0 +1,507 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrAtlasTextBatch.h" + +#include "GrBatchFontCache.h" +#include "GrBatchFlushState.h" +#include "GrBatchTest.h" +#include "GrResourceProvider.h" + +#include "SkDistanceFieldGen.h" +#include "SkGlyphCache.h" + +#include "effects/GrBitmapTextGeoProc.h" +#include "effects/GrDistanceFieldGeoProc.h" + +static inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) { + unsigned r = SkColorGetR(c); + unsigned g = SkColorGetG(c); + unsigned b = SkColorGetB(c); + return GrColorPackRGBA(r, g, b, 0xff); +} + +static const int kDistanceAdjustLumShift = 5; + +SkString GrAtlasTextBatch::dumpInfo() const { + SkString str; + + for (int i = 0; i < fGeoCount; ++i) { + str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n", + i, + fGeoData[i].fColor, + fGeoData[i].fTransX, + fGeoData[i].fTransY, + fGeoData[i].fBlob->fRunCount); + } + + str.append(INHERITED::dumpInfo()); + return str; +} + +void GrAtlasTextBatch::getInvariantOutputColor(GrInitInvariantOutput* out) const { + if (kColorBitmapMask_MaskType == fMaskType) { + out->setUnknownFourComponents(); + } else { + out->setKnownFourComponents(fBatch.fColor); + } +} + +void GrAtlasTextBatch::getInvariantOutputCoverage(GrInitInvariantOutput* out) const { + switch (fMaskType) { + case kGrayscaleDistanceField_MaskType: + case kGrayscaleCoverageMask_MaskType: + out->setUnknownSingleComponent(); + break; + case kLCDCoverageMask_MaskType: + case kLCDDistanceField_MaskType: + out->setUnknownOpaqueFourComponents(); + out->setUsingLCDCoverage(); + break; + case kColorBitmapMask_MaskType: + out->setKnownSingleComponent(0xff); + } +} + +void GrAtlasTextBatch::initBatchTracker(const GrPipelineOptimizations& opt) { + // Handle any color overrides + if (!opt.readsColor()) { + fGeoData[0].fColor = GrColor_ILLEGAL; + } + opt.getOverrideColorIfSet(&fGeoData[0].fColor); + + // setup batch properties + fBatch.fColorIgnored = !opt.readsColor(); + fBatch.fColor = fGeoData[0].fColor; + fBatch.fUsesLocalCoords = opt.readsLocalCoords(); + fBatch.fCoverageIgnored = !opt.readsCoverage(); +} + +void GrAtlasTextBatch::onPrepareDraws(Target* target) { + // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix. + // TODO actually only invert if we don't have RGBA + SkMatrix localMatrix; + if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) { + SkDebugf("Cannot invert viewmatrix\n"); + return; + } + + GrTexture* texture = fFontCache->getTexture(this->maskFormat()); + if (!texture) { + SkDebugf("Could not allocate backing texture for atlas\n"); + return; + } + + bool usesDistanceFields = this->usesDistanceFields(); + GrMaskFormat maskFormat = this->maskFormat(); + bool isLCD = this->isLCD(); + + SkAutoTUnref<const GrGeometryProcessor> gp; + if (usesDistanceFields) { + gp.reset(this->setupDfProcessor(this->viewMatrix(), fFilteredColor, this->color(), + texture)); + } else { + GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode); + gp.reset(GrBitmapTextGeoProc::Create(this->color(), + texture, + params, + maskFormat, + localMatrix, + this->usesLocalCoords())); + } + + FlushInfo flushInfo; + flushInfo.fGlyphsToFlush = 0; + size_t vertexStride = gp->getVertexStride(); + SkASSERT(vertexStride == (usesDistanceFields ? + GetVertexStrideDf(maskFormat, isLCD) : + GetVertexStride(maskFormat))); + + target->initDraw(gp, this->pipeline()); + + int glyphCount = this->numGlyphs(); + const GrVertexBuffer* vertexBuffer; + + void* vertices = target->makeVertexSpace(vertexStride, + glyphCount * kVerticesPerGlyph, + &vertexBuffer, + &flushInfo.fVertexOffset); + flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer)); + flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer()); + if (!vertices || !flushInfo.fVertexBuffer) { + SkDebugf("Could not allocate vertices\n"); + return; + } + + unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices); + + // We cache some values to avoid going to the glyphcache for the same fontScaler twice + // in a row + const SkDescriptor* desc = nullptr; + SkGlyphCache* cache = nullptr; + GrFontScaler* scaler = nullptr; + SkTypeface* typeface = nullptr; + + for (int i = 0; i < fGeoCount; i++) { + Geometry& args = fGeoData[i]; + Blob* blob = args.fBlob; + Run& run = blob->fRuns[args.fRun]; + TextInfo& info = run.fSubRunInfo[args.fSubRun]; + + uint64_t currentAtlasGen = fFontCache->atlasGeneration(maskFormat); + bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen || + info.fStrike->isAbandoned(); + bool regenerateColors; + if (usesDistanceFields) { + regenerateColors = !isLCD && run.fColor != args.fColor; + } else { + regenerateColors = kA8_GrMaskFormat == maskFormat && run.fColor != args.fColor; + } + bool regeneratePositions = args.fTransX != 0.f || args.fTransY != 0.f; + int glyphCount = info.fGlyphEndIndex - info.fGlyphStartIndex; + + // We regenerate both texture coords and colors in the blob itself, and update the + // atlas generation. If we don't end up purging any unused plots, we can avoid + // regenerating the coords. We could take a finer grained approach to updating texture + // coords but its not clear if the extra bookkeeping would offset any gains. + // To avoid looping over the glyphs twice, we do one loop and conditionally update color + // or coords as needed. One final note, if we have to break a run for an atlas eviction + // then we can't really trust the atlas has all of the correct data. Atlas evictions + // should be pretty rare, so we just always regenerate in those cases + if (regenerateTextureCoords || regenerateColors || regeneratePositions) { + // first regenerate texture coordinates / colors if need be + bool brokenRun = false; + + // Because the GrBatchFontCache may evict the strike a blob depends on using for + // generating its texture coords, we have to track whether or not the strike has + // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is + // otherwise we have to get the new strike, and use that to get the correct glyphs. + // Because we do not have the packed ids, and thus can't look up our glyphs in the + // new strike, we instead keep our ref to the old strike and use the packed ids from + // it. These ids will still be valid as long as we hold the ref. When we are done + // updating our cache of the GrGlyph*s, we drop our ref on the old strike + bool regenerateGlyphs = false; + GrBatchTextStrike* strike = nullptr; + if (regenerateTextureCoords) { + info.fBulkUseToken.reset(); + + // We can reuse if we have a valid strike and our descriptors / typeface are the + // same. The override descriptor is only for the non distance field text within + // a run + const SkDescriptor* newDesc = (run.fOverrideDescriptor && !usesDistanceFields) ? + run.fOverrideDescriptor->getDesc() : + run.fDescriptor.getDesc(); + if (!cache || !SkTypeface::Equal(typeface, run.fTypeface) || + !(desc->equals(*newDesc))) { + if (cache) { + SkGlyphCache::AttachCache(cache); + } + desc = newDesc; + cache = SkGlyphCache::DetachCache(run.fTypeface, desc); + scaler = GrTextContext::GetGrFontScaler(cache); + strike = info.fStrike; + typeface = run.fTypeface; + } + + if (info.fStrike->isAbandoned()) { + regenerateGlyphs = true; + strike = fFontCache->getStrike(scaler); + } else { + strike = info.fStrike; + } + } + + for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) { + if (regenerateTextureCoords) { + size_t glyphOffset = glyphIdx + info.fGlyphStartIndex; + + GrGlyph* glyph = blob->fGlyphs[glyphOffset]; + GrGlyph::PackedID id = glyph->fPackedID; + const SkGlyph& skGlyph = scaler->grToSkGlyph(id); + if (regenerateGlyphs) { + // Get the id from the old glyph, and use the new strike to lookup + // the glyph. + blob->fGlyphs[glyphOffset] = strike->getGlyph(skGlyph, id, maskFormat, + scaler); + } + glyph = blob->fGlyphs[glyphOffset]; + SkASSERT(glyph); + SkASSERT(id == glyph->fPackedID); + // We want to be able to assert this but cannot for testing purposes. + // once skbug:4143 has landed we can revist this assert + //SkASSERT(glyph->fMaskFormat == this->maskFormat()); + + if (!fFontCache->hasGlyph(glyph) && + !strike->addGlyphToAtlas(target, glyph, scaler, skGlyph, maskFormat)) { + this->flush(target, &flushInfo); + target->initDraw(gp, this->pipeline()); + brokenRun = glyphIdx > 0; + + SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(target, + glyph, + scaler, + skGlyph, + maskFormat); + SkASSERT(success); + } + fFontCache->addGlyphToBulkAndSetUseToken(&info.fBulkUseToken, glyph, + target->currentToken()); + + // Texture coords are the last vertex attribute so we get a pointer to the + // first one and then map with stride in regenerateTextureCoords + intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); + vertex += info.fVertexStartIndex; + vertex += vertexStride * glyphIdx * kVerticesPerGlyph; + vertex += vertexStride - sizeof(SkIPoint16); + + this->regenerateTextureCoords(glyph, vertex, vertexStride); + } + + if (regenerateColors) { + intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); + vertex += info.fVertexStartIndex; + vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint); + this->regenerateColors(vertex, vertexStride, args.fColor); + } + + if (regeneratePositions) { + intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices); + vertex += info.fVertexStartIndex; + vertex += vertexStride * glyphIdx * kVerticesPerGlyph; + SkScalar transX = args.fTransX; + SkScalar transY = args.fTransY; + this->regeneratePositions(vertex, vertexStride, transX, transY); + } + flushInfo.fGlyphsToFlush++; + } + + // We my have changed the color so update it here + run.fColor = args.fColor; + if (regenerateTextureCoords) { + if (regenerateGlyphs) { + info.fStrike.reset(SkRef(strike)); + } + info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration : + fFontCache->atlasGeneration(maskFormat); + } + } else { + flushInfo.fGlyphsToFlush += glyphCount; + + // set use tokens for all of the glyphs in our subrun. This is only valid if we + // have a valid atlas generation + fFontCache->setUseTokenBulk(info.fBulkUseToken, target->currentToken(), maskFormat); + } + + // now copy all vertices + size_t byteCount = info.fVertexEndIndex - info.fVertexStartIndex; + memcpy(currVertex, blob->fVertices + info.fVertexStartIndex, byteCount); + + currVertex += byteCount; + } + // Make sure to attach the last cache if applicable + if (cache) { + SkGlyphCache::AttachCache(cache); + } + this->flush(target, &flushInfo); +} + +void GrAtlasTextBatch::regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, + size_t vertexStride) { + int width = glyph->fBounds.width(); + int height = glyph->fBounds.height(); + + int u0, v0, u1, v1; + if (this->usesDistanceFields()) { + u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset; + v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset; + u1 = u0 + width - 2 * SK_DistanceFieldInset; + v1 = v0 + height - 2 * SK_DistanceFieldInset; + } else { + u0 = glyph->fAtlasLocation.fX; + v0 = glyph->fAtlasLocation.fY; + u1 = u0 + width; + v1 = v0 + height; + } + + SkIPoint16* textureCoords; + // V0 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u0, v0); + vertex += vertexStride; + + // V1 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u0, v1); + vertex += vertexStride; + + // V2 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u1, v1); + vertex += vertexStride; + + // V3 + textureCoords = reinterpret_cast<SkIPoint16*>(vertex); + textureCoords->set(u1, v0); +} + +void GrAtlasTextBatch::regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) { + for (int i = 0; i < kVerticesPerGlyph; i++) { + SkColor* vcolor = reinterpret_cast<SkColor*>(vertex); + *vcolor = color; + vertex += vertexStride; + } +} + +void GrAtlasTextBatch::regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX, + SkScalar transY) { + for (int i = 0; i < kVerticesPerGlyph; i++) { + SkPoint* point = reinterpret_cast<SkPoint*>(vertex); + point->fX += transX; + point->fY += transY; + vertex += vertexStride; + } +} + +void GrAtlasTextBatch::flush(GrVertexBatch::Target* target, FlushInfo* flushInfo) { + GrVertices vertices; + int maxGlyphsPerDraw = flushInfo->fIndexBuffer->maxQuads(); + vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer, + flushInfo->fIndexBuffer, flushInfo->fVertexOffset, + kVerticesPerGlyph, kIndicesPerGlyph, flushInfo->fGlyphsToFlush, + maxGlyphsPerDraw); + target->draw(vertices); + flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush; + flushInfo->fGlyphsToFlush = 0; +} + +bool GrAtlasTextBatch::onCombineIfPossible(GrBatch* t, const GrCaps& caps) { + GrAtlasTextBatch* that = t->cast<GrAtlasTextBatch>(); + if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(), + that->bounds(), caps)) { + return false; + } + + if (fMaskType != that->fMaskType) { + return false; + } + + if (!this->usesDistanceFields()) { + // TODO we can often batch across LCD text if we have dual source blending and don't + // have to use the blend constant + if (kGrayscaleCoverageMask_MaskType != fMaskType && this->color() != that->color()) { + return false; + } + if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return false; + } + } else { + if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { + return false; + } + + if (fFilteredColor != that->fFilteredColor) { + return false; + } + + if (fUseBGR != that->fUseBGR) { + return false; + } + + // TODO see note above + if (kLCDDistanceField_MaskType == fMaskType && this->color() != that->color()) { + return false; + } + } + + fBatch.fNumGlyphs += that->numGlyphs(); + + // Reallocate space for geo data if necessary and then import that's geo data. + int newGeoCount = that->fGeoCount + fGeoCount; + // We assume (and here enforce) that the allocation size is the smallest power of two that + // is greater than or equal to the number of geometries (and at least + // kMinGeometryAllocated). + int newAllocSize = GrNextPow2(newGeoCount); + int currAllocSize = SkTMax<int>(kMinGeometryAllocated, GrNextPow2(fGeoCount)); + + if (newGeoCount > currAllocSize) { + fGeoData.realloc(newAllocSize); + } + + memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry)); + // We steal the ref on the blobs from the other TextBatch and set its count to 0 so that + // it doesn't try to unref them. +#ifdef SK_DEBUG + for (int i = 0; i < that->fGeoCount; ++i) { + that->fGeoData.get()[i].fBlob = (Blob*)0x1; + } +#endif + that->fGeoCount = 0; + fGeoCount = newGeoCount; + + this->joinBounds(that->bounds()); + return true; +} + +// TODO just use class params +// TODO trying to figure out why lcd is so whack +GrGeometryProcessor* GrAtlasTextBatch::setupDfProcessor(const SkMatrix& viewMatrix, + SkColor filteredColor, + GrColor color, GrTexture* texture) { + GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kBilerp_FilterMode); + bool isLCD = this->isLCD(); + // set up any flags + uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; + + // see if we need to create a new effect + if (isLCD) { + flags |= kUseLCD_DistanceFieldEffectFlag; + flags |= viewMatrix.rectStaysRect() ? kRectToRect_DistanceFieldEffectFlag : 0; + flags |= fUseBGR ? kBGR_DistanceFieldEffectFlag : 0; + + GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor); + + float redCorrection = + (*fDistanceAdjustTable)[GrColorUnpackR(colorNoPreMul) >> kDistanceAdjustLumShift]; + float greenCorrection = + (*fDistanceAdjustTable)[GrColorUnpackG(colorNoPreMul) >> kDistanceAdjustLumShift]; + float blueCorrection = + (*fDistanceAdjustTable)[GrColorUnpackB(colorNoPreMul) >> kDistanceAdjustLumShift]; + GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust = + GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(redCorrection, + greenCorrection, + blueCorrection); + + return GrDistanceFieldLCDTextGeoProc::Create(color, + viewMatrix, + texture, + params, + widthAdjust, + flags, + this->usesLocalCoords()); + } else { + flags |= kColorAttr_DistanceFieldEffectFlag; +#ifdef SK_GAMMA_APPLY_TO_A8 + U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, filteredColor); + float correction = (*fDistanceAdjustTable)[lum >> kDistanceAdjustLumShift]; + return GrDistanceFieldA8TextGeoProc::Create(color, + viewMatrix, + texture, + params, + correction, + flags, + this->usesLocalCoords()); +#else + return GrDistanceFieldA8TextGeoProc::Create(color, + viewMatrix, + texture, + params, + flags, + this->usesLocalCoords()); +#endif + } + +} diff --git a/src/gpu/batches/GrAtlasTextBatch.h b/src/gpu/batches/GrAtlasTextBatch.h new file mode 100644 index 0000000000..63dfa30e6b --- /dev/null +++ b/src/gpu/batches/GrAtlasTextBatch.h @@ -0,0 +1,227 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrAtlasTextBatch_DEFINED +#define GrAtlasTextBatch_DEFINED + +#include "batches/GrVertexBatch.h" + +#include "GrAtlasTextContext.h" + +class GrAtlasTextBatch : public GrVertexBatch { +public: + DEFINE_BATCH_CLASS_ID + static const size_t kLCDTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16); + + // position + local coord + static const size_t kColorTextVASize = sizeof(SkPoint) + sizeof(SkIPoint16); + static const size_t kGrayTextVASize = sizeof(SkPoint) + sizeof(GrColor) + sizeof(SkIPoint16); + static const int kVerticesPerGlyph = 4; + static const int kIndicesPerGlyph = 6; + + typedef GrAtlasTextContext::DistanceAdjustTable DistanceAdjustTable; + typedef GrAtlasTextBlob Blob; + typedef Blob::Run Run; + typedef Run::SubRunInfo TextInfo; + struct Geometry { + Blob* fBlob; + int fRun; + int fSubRun; + GrColor fColor; + SkScalar fTransX; + SkScalar fTransY; + }; + + static GrAtlasTextBatch* CreateBitmap(GrMaskFormat maskFormat, int glyphCount, + GrBatchFontCache* fontCache) { + GrAtlasTextBatch* batch = new GrAtlasTextBatch; + + batch->fFontCache = fontCache; + switch (maskFormat) { + case kA8_GrMaskFormat: + batch->fMaskType = kGrayscaleCoverageMask_MaskType; + break; + case kA565_GrMaskFormat: + batch->fMaskType = kLCDCoverageMask_MaskType; + break; + case kARGB_GrMaskFormat: + batch->fMaskType = kColorBitmapMask_MaskType; + break; + } + batch->fBatch.fNumGlyphs = glyphCount; + batch->fGeoCount = 1; + batch->fFilteredColor = 0; + batch->fFontCache = fontCache; + batch->fUseBGR = false; + return batch; + } + + static GrAtlasTextBatch* CreateDistanceField(int glyphCount, GrBatchFontCache* fontCache, + const DistanceAdjustTable* distanceAdjustTable, + SkColor filteredColor, bool isLCD, + bool useBGR) { + GrAtlasTextBatch* batch = new GrAtlasTextBatch; + + batch->fFontCache = fontCache; + batch->fMaskType = isLCD ? kLCDDistanceField_MaskType : kGrayscaleDistanceField_MaskType; + batch->fDistanceAdjustTable.reset(SkRef(distanceAdjustTable)); + batch->fFilteredColor = filteredColor; + batch->fUseBGR = useBGR; + batch->fBatch.fNumGlyphs = glyphCount; + batch->fGeoCount = 1; + return batch; + } + + // to avoid even the initial copy of the struct, we have a getter for the first item which + // is used to seed the batch with its initial geometry. After seeding, the client should call + // init() so the Batch can initialize itself + Geometry& geometry() { return fGeoData[0]; } + + void init() { + const Geometry& geo = fGeoData[0]; + fBatch.fColor = geo.fColor; + fBatch.fViewMatrix = geo.fBlob->fViewMatrix; + + // We don't yet position distance field text on the cpu, so we have to map the vertex bounds + // into device space + const Run& run = geo.fBlob->fRuns[geo.fRun]; + if (run.fSubRunInfo[geo.fSubRun].fDrawAsDistanceFields) { + SkRect bounds = run.fVertexBounds; + fBatch.fViewMatrix.mapRect(&bounds); + this->setBounds(bounds); + } else { + this->setBounds(run.fVertexBounds); + } + } + + const char* name() const override { return "TextBatch"; } + + SkString dumpInfo() const override; + + void getInvariantOutputColor(GrInitInvariantOutput* out) const override; + + void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override; + + static size_t GetVertexStride(GrMaskFormat maskFormat) { + switch (maskFormat) { + case kA8_GrMaskFormat: + return kGrayTextVASize; + case kARGB_GrMaskFormat: + return kColorTextVASize; + default: + return kLCDTextVASize; + } + } + + static size_t GetVertexStrideDf(GrMaskFormat maskFormat, bool useLCDText) { + SkASSERT(maskFormat == kA8_GrMaskFormat); + if (useLCDText) { + return kLCDTextVASize; + } else { + return kGrayTextVASize; + } + } + +private: + void initBatchTracker(const GrPipelineOptimizations& opt) override; + + struct FlushInfo { + SkAutoTUnref<const GrVertexBuffer> fVertexBuffer; + SkAutoTUnref<const GrIndexBuffer> fIndexBuffer; + int fGlyphsToFlush; + int fVertexOffset; + }; + + void onPrepareDraws(Target* target) override; + + GrAtlasTextBatch() : INHERITED(ClassID()) {} // initialized in factory functions. + + ~GrAtlasTextBatch() { + for (int i = 0; i < fGeoCount; i++) { + fGeoData[i].fBlob->unref(); + } + } + + GrMaskFormat maskFormat() const { + switch (fMaskType) { + case kLCDCoverageMask_MaskType: + return kA565_GrMaskFormat; + case kColorBitmapMask_MaskType: + return kARGB_GrMaskFormat; + case kGrayscaleCoverageMask_MaskType: + case kGrayscaleDistanceField_MaskType: + case kLCDDistanceField_MaskType: + return kA8_GrMaskFormat; + } + return kA8_GrMaskFormat; // suppress warning + } + + bool usesDistanceFields() const { + return kGrayscaleDistanceField_MaskType == fMaskType || + kLCDDistanceField_MaskType == fMaskType; + } + + bool isLCD() const { + return kLCDCoverageMask_MaskType == fMaskType || + kLCDDistanceField_MaskType == fMaskType; + } + + inline void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride); + + inline void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color); + + inline void regeneratePositions(intptr_t vertex, size_t vertexStride, SkScalar transX, + SkScalar transY); + + inline void flush(GrVertexBatch::Target* target, FlushInfo* flushInfo); + + GrColor color() const { return fBatch.fColor; } + const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; } + bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } + int numGlyphs() const { return fBatch.fNumGlyphs; } + + bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override; + + // TODO just use class params + // TODO trying to figure out why lcd is so whack + GrGeometryProcessor* setupDfProcessor(const SkMatrix& viewMatrix, SkColor filteredColor, + GrColor color, GrTexture* texture); + + struct BatchTracker { + GrColor fColor; + SkMatrix fViewMatrix; + bool fUsesLocalCoords; + bool fColorIgnored; + bool fCoverageIgnored; + int fNumGlyphs; + }; + + BatchTracker fBatch; + // The minimum number of Geometry we will try to allocate. + enum { kMinGeometryAllocated = 4 }; + SkAutoSTMalloc<kMinGeometryAllocated, Geometry> fGeoData; + int fGeoCount; + + enum MaskType { + kGrayscaleCoverageMask_MaskType, + kLCDCoverageMask_MaskType, + kColorBitmapMask_MaskType, + kGrayscaleDistanceField_MaskType, + kLCDDistanceField_MaskType, + } fMaskType; + bool fUseBGR; // fold this into the enum? + + GrBatchFontCache* fFontCache; + + // Distance field properties + SkAutoTUnref<const DistanceAdjustTable> fDistanceAdjustTable; + SkColor fFilteredColor; + + typedef GrVertexBatch INHERITED; +}; + +#endif |