1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrAtlasTextOp.h"
9
10#include "GrContext.h"
11#include "GrOpFlushState.h"
12#include "GrResourceProvider.h"
13
14#include "SkGlyphCache.h"
15#include "SkMathPriv.h"
16
17#include "effects/GrBitmapTextGeoProc.h"
18#include "effects/GrDistanceFieldGeoProc.h"
19#include "text/GrAtlasGlyphCache.h"
20
21///////////////////////////////////////////////////////////////////////////////////////////////////
22
23static const int kDistanceAdjustLumShift = 5;
24
25SkString GrAtlasTextOp::dumpInfo() const {
26    SkString str;
27
28    for (int i = 0; i < fGeoCount; ++i) {
29        str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n",
30                    i,
31                    fGeoData[i].fColor,
32                    fGeoData[i].fX,
33                    fGeoData[i].fY,
34                    fGeoData[i].fBlob->runCount());
35    }
36
37    str += fProcessors.dumpProcessors();
38    str += INHERITED::dumpInfo();
39    return str;
40}
41
42GrDrawOp::FixedFunctionFlags GrAtlasTextOp::fixedFunctionFlags() const {
43    return FixedFunctionFlags::kNone;
44}
45
46GrDrawOp::RequiresDstTexture GrAtlasTextOp::finalize(const GrCaps& caps,
47                                                     const GrAppliedClip* clip) {
48    GrProcessorAnalysisCoverage coverage;
49    GrProcessorAnalysisColor color;
50    if (kColorBitmapMask_MaskType == fMaskType) {
51        color.setToUnknown();
52    } else {
53        color.setToConstant(fColor);
54    }
55    switch (fMaskType) {
56        case kGrayscaleCoverageMask_MaskType:
57        case kAliasedDistanceField_MaskType:
58        case kGrayscaleDistanceField_MaskType:
59            coverage = GrProcessorAnalysisCoverage::kSingleChannel;
60            break;
61        case kLCDCoverageMask_MaskType:
62        case kLCDDistanceField_MaskType:
63        case kLCDBGRDistanceField_MaskType:
64            coverage = GrProcessorAnalysisCoverage::kLCD;
65            break;
66        case kColorBitmapMask_MaskType:
67            coverage = GrProcessorAnalysisCoverage::kNone;
68            break;
69    }
70    auto analysis = fProcessors.finalize(color, coverage, clip, false, caps, &fColor);
71    fUsesLocalCoords = analysis.usesLocalCoords();
72    fCanCombineOnTouchOrOverlap =
73            !analysis.requiresDstTexture() &&
74            !(fProcessors.xferProcessor() && fProcessors.xferProcessor()->xferBarrierType(caps));
75    return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
76}
77
78void GrAtlasTextOp::onPrepareDraws(Target* target) const {
79    // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
80    // TODO actually only invert if we don't have RGBA
81    SkMatrix localMatrix;
82    if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
83        SkDebugf("Cannot invert viewmatrix\n");
84        return;
85    }
86
87    sk_sp<GrTextureProxy> proxy = fFontCache->getProxy(this->maskFormat());
88    if (!proxy) {
89        SkDebugf("Could not allocate backing texture for atlas\n");
90        return;
91    }
92
93    GrMaskFormat maskFormat = this->maskFormat();
94
95    FlushInfo flushInfo;
96    flushInfo.fPipeline = target->makePipeline(fSRGBFlags, &fProcessors);
97    if (this->usesDistanceFields()) {
98        flushInfo.fGeometryProcessor =
99                this->setupDfProcessor(this->viewMatrix(),
100                                       fLuminanceColor, this->color(), std::move(proxy));
101    } else {
102        GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
103        flushInfo.fGeometryProcessor =
104                GrBitmapTextGeoProc::Make(this->color(), std::move(proxy), params, maskFormat,
105                                          localMatrix, this->usesLocalCoords());
106    }
107
108    flushInfo.fGlyphsToFlush = 0;
109    size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
110    SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat));
111
112    int glyphCount = this->numGlyphs();
113    const GrBuffer* vertexBuffer;
114
115    void* vertices = target->makeVertexSpace(
116            vertexStride, glyphCount * kVerticesPerGlyph, &vertexBuffer, &flushInfo.fVertexOffset);
117    flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
118    flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
119    if (!vertices || !flushInfo.fVertexBuffer) {
120        SkDebugf("Could not allocate vertices\n");
121        return;
122    }
123
124    unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
125
126    GrBlobRegenHelper helper(this, target, &flushInfo);
127    SkAutoGlyphCache glyphCache;
128    for (int i = 0; i < fGeoCount; i++) {
129        const Geometry& args = fGeoData[i];
130        Blob* blob = args.fBlob;
131        size_t byteCount;
132        void* blobVertices;
133        int subRunGlyphCount;
134        blob->regenInOp(target, fFontCache, &helper, args.fRun, args.fSubRun, &glyphCache,
135                        vertexStride, args.fViewMatrix, args.fX, args.fY, args.fColor,
136                        &blobVertices, &byteCount, &subRunGlyphCount);
137
138        // now copy all vertices
139        memcpy(currVertex, blobVertices, byteCount);
140
141        currVertex += byteCount;
142    }
143
144    this->flush(target, &flushInfo);
145}
146
147void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
148    GrMesh mesh(GrPrimitiveType::kTriangles);
149    int maxGlyphsPerDraw =
150            static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
151    mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph, kVerticesPerGlyph,
152                             flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
153    mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
154    target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh);
155    flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
156    flushInfo->fGlyphsToFlush = 0;
157}
158
159bool GrAtlasTextOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
160    GrAtlasTextOp* that = t->cast<GrAtlasTextOp>();
161    if (fProcessors != that->fProcessors) {
162        return false;
163    }
164
165    if (!fCanCombineOnTouchOrOverlap && GrRectsTouchOrOverlap(this->bounds(), that->bounds())) {
166        return false;
167    }
168
169    if (fMaskType != that->fMaskType) {
170        return false;
171    }
172
173    if (!this->usesDistanceFields()) {
174        if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) {
175            return false;
176        }
177        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
178            return false;
179        }
180    } else {
181        if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
182            return false;
183        }
184
185        if (fLuminanceColor != that->fLuminanceColor) {
186            return false;
187        }
188    }
189
190    fNumGlyphs += that->numGlyphs();
191
192    // Reallocate space for geo data if necessary and then import that's geo data.
193    int newGeoCount = that->fGeoCount + fGeoCount;
194    // We assume (and here enforce) that the allocation size is the smallest power of two that
195    // is greater than or equal to the number of geometries (and at least
196    // kMinGeometryAllocated).
197    int newAllocSize = GrNextPow2(newGeoCount);
198    int currAllocSize = SkTMax<int>(kMinGeometryAllocated, GrNextPow2(fGeoCount));
199
200    if (newGeoCount > currAllocSize) {
201        fGeoData.realloc(newAllocSize);
202    }
203
204    // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that
205    // it doesn't try to unref them.
206    memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry));
207#ifdef SK_DEBUG
208    for (int i = 0; i < that->fGeoCount; ++i) {
209        that->fGeoData.get()[i].fBlob = (Blob*)0x1;
210    }
211#endif
212    that->fGeoCount = 0;
213    fGeoCount = newGeoCount;
214
215    this->joinBounds(*that);
216    return true;
217}
218
219// TODO just use class params
220// TODO trying to figure out why lcd is so whack
221sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor(const SkMatrix& viewMatrix,
222                                                           SkColor luminanceColor,
223                                                           GrColor color,
224                                                           sk_sp<GrTextureProxy> proxy) const {
225    GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
226    bool isLCD = this->isLCD();
227    // set up any flags
228    uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
229    flags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
230    flags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
231    flags |= (kAliasedDistanceField_MaskType == fMaskType) ? kAliased_DistanceFieldEffectFlag : 0;
232
233    // see if we need to create a new effect
234    if (isLCD) {
235        flags |= kUseLCD_DistanceFieldEffectFlag;
236        flags |= (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0;
237
238        float redCorrection = fDistanceAdjustTable->getAdjustment(
239                SkColorGetR(luminanceColor) >> kDistanceAdjustLumShift,
240                fUseGammaCorrectDistanceTable);
241        float greenCorrection = fDistanceAdjustTable->getAdjustment(
242                SkColorGetG(luminanceColor) >> kDistanceAdjustLumShift,
243                fUseGammaCorrectDistanceTable);
244        float blueCorrection = fDistanceAdjustTable->getAdjustment(
245                SkColorGetB(luminanceColor) >> kDistanceAdjustLumShift,
246                fUseGammaCorrectDistanceTable);
247        GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
248                GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
249                        redCorrection, greenCorrection, blueCorrection);
250
251        return GrDistanceFieldLCDTextGeoProc::Make(color, viewMatrix, std::move(proxy), params,
252                                                   widthAdjust, flags, this->usesLocalCoords());
253    } else {
254#ifdef SK_GAMMA_APPLY_TO_A8
255        float correction = 0;
256        if (kAliasedDistanceField_MaskType != fMaskType) {
257            U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, luminanceColor);
258            correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
259                                                             fUseGammaCorrectDistanceTable);
260        }
261        return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, std::move(proxy), params,
262                                                  correction, flags, this->usesLocalCoords());
263#else
264        return GrDistanceFieldA8TextGeoProc::Make(color,
265                                                  viewMatrix, std::move(proxy),
266                                                  params, flags, this->usesLocalCoords());
267#endif
268    }
269}
270
271void GrBlobRegenHelper::flush() { fOp->flush(fTarget, fFlushInfo); }
272