/* * 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 "GrAtlasTextOp.h" #include "GrContext.h" #include "GrOpFlushState.h" #include "GrResourceProvider.h" #include "SkGlyphCache.h" #include "SkMathPriv.h" #include "effects/GrBitmapTextGeoProc.h" #include "effects/GrDistanceFieldGeoProc.h" #include "text/GrAtlasGlyphCache.h" /////////////////////////////////////////////////////////////////////////////////////////////////// static const int kDistanceAdjustLumShift = 5; SkString GrAtlasTextOp::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].fX, fGeoData[i].fY, fGeoData[i].fBlob->runCount()); } str += fProcessors.dumpProcessors(); str += INHERITED::dumpInfo(); return str; } GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const { return FixedFunctionFlags::kNone; } GrDrawOp::RequiresDstTexture GrAtlasTextOp::finalize(const GrCaps& caps, const GrAppliedClip* clip, GrPixelConfigIsClamped dstIsClamped) { GrProcessorAnalysisCoverage coverage; GrProcessorAnalysisColor color; if (kColorBitmapMask_MaskType == fMaskType) { color.setToUnknown(); } else { color.setToConstant(fColor); } switch (fMaskType) { case kGrayscaleCoverageMask_MaskType: case kAliasedDistanceField_MaskType: case kGrayscaleDistanceField_MaskType: coverage = GrProcessorAnalysisCoverage::kSingleChannel; break; case kLCDCoverageMask_MaskType: case kLCDDistanceField_MaskType: case kLCDBGRDistanceField_MaskType: coverage = GrProcessorAnalysisCoverage::kLCD; break; case kColorBitmapMask_MaskType: coverage = GrProcessorAnalysisCoverage::kNone; break; } auto analysis = fProcessors.finalize(color, coverage, clip, false, caps, dstIsClamped, &fColor); fUsesLocalCoords = analysis.usesLocalCoords(); fCanCombineOnTouchOrOverlap = !analysis.requiresDstTexture() && !(fProcessors.xferProcessor() && fProcessors.xferProcessor()->xferBarrierType(caps)); return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo; } void GrAtlasTextOp::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; } GrMaskFormat maskFormat = this->maskFormat(); uint32_t atlasPageCount = fFontCache->getAtlasPageCount(maskFormat); const sk_sp* proxies = fFontCache->getProxies(maskFormat); if (!atlasPageCount || !proxies[0]) { SkDebugf("Could not allocate backing texture for atlas\n"); return; } FlushInfo flushInfo; flushInfo.fPipeline = target->makePipeline(fSRGBFlags, std::move(fProcessors), target->detachAppliedClip()); if (this->usesDistanceFields()) { flushInfo.fGeometryProcessor = this->setupDfProcessor(this->viewMatrix(), fLuminanceColor, this->color(), proxies); } else { flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make( this->color(), proxies, GrSamplerState::ClampNearest(), maskFormat, localMatrix, this->usesLocalCoords()); } flushInfo.fGlyphsToFlush = 0; size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride(); SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat)); int glyphCount = this->numGlyphs(); const GrBuffer* 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(vertices); GrBlobRegenHelper helper(this, target, &flushInfo); SkAutoGlyphCache glyphCache; for (int i = 0; i < fGeoCount; i++) { const Geometry& args = fGeoData[i]; Blob* blob = args.fBlob; size_t byteCount; void* blobVertices; int subRunGlyphCount; blob->regenInOp(target, fFontCache, &helper, args.fRun, args.fSubRun, &glyphCache, vertexStride, args.fViewMatrix, args.fX, args.fY, args.fColor, &blobVertices, &byteCount, &subRunGlyphCount); // now copy all vertices memcpy(currVertex, blobVertices, byteCount); currVertex += byteCount; } this->flush(target, &flushInfo); } void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const { GrGeometryProcessor* gp = flushInfo->fGeometryProcessor.get(); GrMaskFormat maskFormat = this->maskFormat(); if (gp->numTextureSamplers() != (int)fFontCache->getAtlasPageCount(maskFormat)) { // During preparation the number of atlas pages has increased. // Update the proxies used in the GP to match. if (this->usesDistanceFields()) { if (this->isLCD()) { reinterpret_cast(gp)->addNewProxies( fFontCache->getProxies(maskFormat), GrSamplerState::ClampBilerp()); } else { reinterpret_cast(gp)->addNewProxies( fFontCache->getProxies(maskFormat), GrSamplerState::ClampBilerp()); } } else { reinterpret_cast(gp)->addNewProxies( fFontCache->getProxies(maskFormat), GrSamplerState::ClampNearest()); } } GrMesh mesh(GrPrimitiveType::kTriangles); int maxGlyphsPerDraw = static_cast(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6); mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph, kVerticesPerGlyph, flushInfo->fGlyphsToFlush, maxGlyphsPerDraw); mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset); target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh); flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush; flushInfo->fGlyphsToFlush = 0; } bool GrAtlasTextOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) { GrAtlasTextOp* that = t->cast(); if (fProcessors != that->fProcessors) { return false; } if (!fCanCombineOnTouchOrOverlap && GrRectsTouchOrOverlap(this->bounds(), that->bounds())) { return false; } if (fMaskType != that->fMaskType) { return false; } if (!this->usesDistanceFields()) { if (kColorBitmapMask_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 (fLuminanceColor != that->fLuminanceColor) { return false; } } 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(kMinGeometryAllocated, GrNextPow2(fGeoCount)); if (newGeoCount > currAllocSize) { fGeoData.realloc(newAllocSize); } // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that // it doesn't try to unref them. memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry)); #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); return true; } // TODO just use class params // TODO trying to figure out why lcd is so whack sk_sp GrAtlasTextOp::setupDfProcessor( const SkMatrix& viewMatrix, SkColor luminanceColor, GrColor color, const sk_sp p[kMaxTextures]) const { bool isLCD = this->isLCD(); // set up any flags uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; flags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0; flags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0; flags |= (kAliasedDistanceField_MaskType == fMaskType) ? kAliased_DistanceFieldEffectFlag : 0; // see if we need to create a new effect if (isLCD) { flags |= kUseLCD_DistanceFieldEffectFlag; flags |= (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0; float redCorrection = fDistanceAdjustTable->getAdjustment( SkColorGetR(luminanceColor) >> kDistanceAdjustLumShift, fUseGammaCorrectDistanceTable); float greenCorrection = fDistanceAdjustTable->getAdjustment( SkColorGetG(luminanceColor) >> kDistanceAdjustLumShift, fUseGammaCorrectDistanceTable); float blueCorrection = fDistanceAdjustTable->getAdjustment( SkColorGetB(luminanceColor) >> kDistanceAdjustLumShift, fUseGammaCorrectDistanceTable); GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust = GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make( redCorrection, greenCorrection, blueCorrection); return GrDistanceFieldLCDTextGeoProc::Make(color, viewMatrix, p, GrSamplerState::ClampBilerp(), widthAdjust, flags, this->usesLocalCoords()); } else { #ifdef SK_GAMMA_APPLY_TO_A8 float correction = 0; if (kAliasedDistanceField_MaskType != fMaskType) { U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, luminanceColor); correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift, fUseGammaCorrectDistanceTable); } return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, p, GrSamplerState::ClampBilerp(), correction, flags, this->usesLocalCoords()); #else return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, p, GrSamplerState::ClampBilerp(), flags, this->usesLocalCoords()); #endif } } void GrBlobRegenHelper::flush() { fOp->flush(fTarget, fFlushInfo); }