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 inline GrColor skcolor_to_grcolor_nopremultiply(SkColor c) {
24    unsigned r = SkColorGetR(c);
25    unsigned g = SkColorGetG(c);
26    unsigned b = SkColorGetB(c);
27    return GrColorPackRGBA(r, g, b, 0xff);
28}
29
30static const int kDistanceAdjustLumShift = 5;
31
32SkString GrAtlasTextOp::dumpInfo() const {
33    SkString str;
34
35    for (int i = 0; i < fGeoCount; ++i) {
36        str.appendf("%d: Color: 0x%08x Trans: %.2f,%.2f Runs: %d\n",
37                    i,
38                    fGeoData[i].fColor,
39                    fGeoData[i].fX,
40                    fGeoData[i].fY,
41                    fGeoData[i].fBlob->runCount());
42    }
43
44    str.append(DumpPipelineInfo(*this->pipeline()));
45    str.append(INHERITED::dumpInfo());
46    return str;
47}
48
49void GrAtlasTextOp::getFragmentProcessorAnalysisInputs(GrPipelineAnalysisColor* color,
50                                                       GrPipelineAnalysisCoverage* coverage) const {
51    if (kColorBitmapMask_MaskType == fMaskType) {
52        color->setToUnknown();
53    } else {
54        color->setToConstant(fColor);
55    }
56    switch (fMaskType) {
57        case kGrayscaleDistanceField_MaskType:
58        case kGrayscaleCoverageMask_MaskType:
59            *coverage = GrPipelineAnalysisCoverage::kSingleChannel;
60            break;
61        case kLCDCoverageMask_MaskType:
62        case kLCDDistanceField_MaskType:
63            *coverage = GrPipelineAnalysisCoverage::kLCD;
64            break;
65        case kColorBitmapMask_MaskType:
66            *coverage = GrPipelineAnalysisCoverage::kNone;
67            break;
68    }
69}
70
71void GrAtlasTextOp::applyPipelineOptimizations(const GrPipelineOptimizations& optimizations) {
72    optimizations.getOverrideColorIfSet(&fGeoData[0].fColor);
73
74    fColor = fGeoData[0].fColor;
75    fUsesLocalCoords = optimizations.readsLocalCoords();
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    if (this->usesDistanceFields()) {
97        flushInfo.fGeometryProcessor =
98                this->setupDfProcessor(fFontCache->context()->resourceProvider(),
99                                       this->viewMatrix(),
100                                       fFilteredColor, this->color(), std::move(proxy));
101    } else {
102        GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kNone_FilterMode);
103        flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
104                fFontCache->context()->resourceProvider(),
105                this->color(), std::move(proxy), params,
106                maskFormat, localMatrix, this->usesLocalCoords());
107    }
108
109    flushInfo.fGlyphsToFlush = 0;
110    size_t vertexStride = flushInfo.fGeometryProcessor->getVertexStride();
111    SkASSERT(vertexStride == GrAtlasTextBlob::GetVertexStride(maskFormat));
112
113    int glyphCount = this->numGlyphs();
114    const GrBuffer* vertexBuffer;
115
116    void* vertices = target->makeVertexSpace(
117            vertexStride, glyphCount * kVerticesPerGlyph, &vertexBuffer, &flushInfo.fVertexOffset);
118    flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
119    flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
120    if (!vertices || !flushInfo.fVertexBuffer) {
121        SkDebugf("Could not allocate vertices\n");
122        return;
123    }
124
125    unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
126
127    GrBlobRegenHelper helper(this, target, &flushInfo);
128    SkAutoGlyphCache glyphCache;
129    for (int i = 0; i < fGeoCount; i++) {
130        const Geometry& args = fGeoData[i];
131        Blob* blob = args.fBlob;
132        size_t byteCount;
133        void* blobVertices;
134        int subRunGlyphCount;
135        blob->regenInOp(target, fFontCache, &helper, args.fRun, args.fSubRun, &glyphCache,
136                        vertexStride, args.fViewMatrix, args.fX, args.fY, args.fColor,
137                        &blobVertices, &byteCount, &subRunGlyphCount);
138
139        // now copy all vertices
140        memcpy(currVertex, blobVertices, byteCount);
141
142        currVertex += byteCount;
143    }
144
145    this->flush(target, &flushInfo);
146}
147
148void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
149    GrMesh mesh;
150    int maxGlyphsPerDraw =
151            static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
152    mesh.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer.get(),
153                       flushInfo->fIndexBuffer.get(), flushInfo->fVertexOffset, kVerticesPerGlyph,
154                       kIndicesPerGlyph, flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
155    target->draw(flushInfo->fGeometryProcessor.get(), mesh);
156    flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
157    flushInfo->fGlyphsToFlush = 0;
158}
159
160bool GrAtlasTextOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
161    GrAtlasTextOp* that = t->cast<GrAtlasTextOp>();
162    if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
163                                that->bounds(), caps)) {
164        return false;
165    }
166
167    if (fMaskType != that->fMaskType) {
168        return false;
169    }
170
171    if (!this->usesDistanceFields()) {
172        if (kColorBitmapMask_MaskType == fMaskType && this->color() != that->color()) {
173            return false;
174        }
175        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
176            return false;
177        }
178    } else {
179        if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
180            return false;
181        }
182
183        if (fFilteredColor != that->fFilteredColor) {
184            return false;
185        }
186
187        if (fUseBGR != that->fUseBGR) {
188            return false;
189        }
190    }
191
192    fNumGlyphs += that->numGlyphs();
193
194    // Reallocate space for geo data if necessary and then import that's geo data.
195    int newGeoCount = that->fGeoCount + fGeoCount;
196    // We assume (and here enforce) that the allocation size is the smallest power of two that
197    // is greater than or equal to the number of geometries (and at least
198    // kMinGeometryAllocated).
199    int newAllocSize = GrNextPow2(newGeoCount);
200    int currAllocSize = SkTMax<int>(kMinGeometryAllocated, GrNextPow2(fGeoCount));
201
202    if (newGeoCount > currAllocSize) {
203        fGeoData.realloc(newAllocSize);
204    }
205
206    // We steal the ref on the blobs from the other AtlasTextOp and set its count to 0 so that
207    // it doesn't try to unref them.
208    memcpy(&fGeoData[fGeoCount], that->fGeoData.get(), that->fGeoCount * sizeof(Geometry));
209#ifdef SK_DEBUG
210    for (int i = 0; i < that->fGeoCount; ++i) {
211        that->fGeoData.get()[i].fBlob = (Blob*)0x1;
212    }
213#endif
214    that->fGeoCount = 0;
215    fGeoCount = newGeoCount;
216
217    this->joinBounds(*that);
218    return true;
219}
220
221// TODO just use class params
222// TODO trying to figure out why lcd is so whack
223sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor(GrResourceProvider* resourceProvider,
224                                                           const SkMatrix& viewMatrix,
225                                                           SkColor filteredColor,
226                                                           GrColor color,
227                                                           sk_sp<GrTextureProxy> proxy) const {
228    GrSamplerParams params(SkShader::kClamp_TileMode, GrSamplerParams::kBilerp_FilterMode);
229    bool isLCD = this->isLCD();
230    // set up any flags
231    uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
232    flags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
233    flags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
234
235    // see if we need to create a new effect
236    if (isLCD) {
237        flags |= kUseLCD_DistanceFieldEffectFlag;
238        flags |= fUseBGR ? kBGR_DistanceFieldEffectFlag : 0;
239
240        GrColor colorNoPreMul = skcolor_to_grcolor_nopremultiply(filteredColor);
241
242        float redCorrection = fDistanceAdjustTable->getAdjustment(
243                GrColorUnpackR(colorNoPreMul) >> kDistanceAdjustLumShift,
244                fUseGammaCorrectDistanceTable);
245        float greenCorrection = fDistanceAdjustTable->getAdjustment(
246                GrColorUnpackG(colorNoPreMul) >> kDistanceAdjustLumShift,
247                fUseGammaCorrectDistanceTable);
248        float blueCorrection = fDistanceAdjustTable->getAdjustment(
249                GrColorUnpackB(colorNoPreMul) >> kDistanceAdjustLumShift,
250                fUseGammaCorrectDistanceTable);
251        GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
252                GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
253                        redCorrection, greenCorrection, blueCorrection);
254
255        return GrDistanceFieldLCDTextGeoProc::Make(resourceProvider,
256                                                   color, viewMatrix, std::move(proxy),
257                                                   params, widthAdjust, flags,
258                                                   this->usesLocalCoords());
259    } else {
260#ifdef SK_GAMMA_APPLY_TO_A8
261        U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, filteredColor);
262        float correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
263                                                               fUseGammaCorrectDistanceTable);
264        return GrDistanceFieldA8TextGeoProc::Make(resourceProvider, color,
265                                                  viewMatrix, std::move(proxy),
266                                                  params, correction, flags,
267                                                  this->usesLocalCoords());
268#else
269        return GrDistanceFieldA8TextGeoProc::Make(resourceProvider, color,
270                                                  viewMatrix, std::move(proxy),
271                                                  params, flags, this->usesLocalCoords());
272#endif
273    }
274}
275
276void GrBlobRegenHelper::flush() { fOp->flush(fTarget, fFlushInfo); }
277