sRGB support for distance field text.
authorbrianosman <brianosman@google.com>
Tue, 12 Apr 2016 19:48:21 +0000 (12:48 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 12 Apr 2016 19:48:21 +0000 (12:48 -0700)
Add a second distance field adjust table that only applies contrast,
not fake-gamma correction. Store a flag in the batch at creation time,
using the same logic we apply elsewhere (render target format, plus
paint flags).

That gets us close, but not as good as bitmap text. The final step is
to use a linear step function (rather than smoothstep) to map distance
to coverage, when we have sRGB output. Smoothstep's nonlinear response
is actually doing some fake-gamma, so it ends up over-correcting when
the output is already gamma-correct.

Results are now very close between L32 (old table, smoothstep) and S32
(contrast-only table, linstep).

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1885613002

Review URL: https://codereview.chromium.org/1885613002

src/gpu/batches/GrAtlasTextBatch.cpp
src/gpu/batches/GrAtlasTextBatch.h
src/gpu/effects/GrDistanceFieldGeoProc.cpp
src/gpu/effects/GrDistanceFieldGeoProc.h
src/gpu/text/GrAtlasTextBlob.cpp
src/gpu/text/GrAtlasTextBlob.h
src/gpu/text/GrDistanceFieldAdjustTable.cpp
src/gpu/text/GrDistanceFieldAdjustTable.h

index ca6b99ea336312d51282339c547b82575ae12f64..03b3ad03632dc808759dbd5c1628cd62a43d637d 100644 (file)
@@ -261,6 +261,7 @@ GrGeometryProcessor* GrAtlasTextBatch::setupDfProcessor(const SkMatrix& viewMatr
     // set up any flags
     uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
     flags |= viewMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
+    flags |= fUseSRGBDistanceTable ? kSRGB_DistanceFieldEffectFlag : 0;
 
     // see if we need to create a new effect
     if (isLCD) {
@@ -269,12 +270,12 @@ GrGeometryProcessor* GrAtlasTextBatch::setupDfProcessor(const SkMatrix& viewMatr
 
         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];
+        float redCorrection = fDistanceAdjustTable->getAdjustment(
+            GrColorUnpackR(colorNoPreMul) >> kDistanceAdjustLumShift, fUseSRGBDistanceTable);
+        float greenCorrection = fDistanceAdjustTable->getAdjustment(
+            GrColorUnpackG(colorNoPreMul) >> kDistanceAdjustLumShift, fUseSRGBDistanceTable);
+        float blueCorrection = fDistanceAdjustTable->getAdjustment(
+            GrColorUnpackB(colorNoPreMul) >> kDistanceAdjustLumShift, fUseSRGBDistanceTable);
         GrDistanceFieldLCDTextGeoProc::DistanceAdjust widthAdjust =
             GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(redCorrection,
                                                                 greenCorrection,
@@ -290,7 +291,8 @@ GrGeometryProcessor* GrAtlasTextBatch::setupDfProcessor(const SkMatrix& viewMatr
     } else {
 #ifdef SK_GAMMA_APPLY_TO_A8
         U8CPU lum = SkColorSpaceLuminance::computeLuminance(SK_GAMMA_EXPONENT, filteredColor);
-        float correction = (*fDistanceAdjustTable)[lum >> kDistanceAdjustLumShift];
+        float correction = fDistanceAdjustTable->getAdjustment(
+            lum >> kDistanceAdjustLumShift, fUseSRGBDistanceTable);
         return GrDistanceFieldA8TextGeoProc::Create(color,
                                                     viewMatrix,
                                                     texture,
index e883fa1d9fff27dd93f1f332d9ba4233f74c9968..629027a0030f551073505a3236c210ce09714fb3 100644 (file)
@@ -58,6 +58,7 @@ public:
     static GrAtlasTextBatch* CreateDistanceField(
                                               int glyphCount, GrBatchFontCache* fontCache,
                                               const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                              bool useSRGBDistanceTable,
                                               SkColor filteredColor, bool isLCD,
                                               bool useBGR) {
         GrAtlasTextBatch* batch = new GrAtlasTextBatch;
@@ -65,6 +66,7 @@ public:
         batch->fFontCache = fontCache;
         batch->fMaskType = isLCD ? kLCDDistanceField_MaskType : kGrayscaleDistanceField_MaskType;
         batch->fDistanceAdjustTable.reset(SkRef(distanceAdjustTable));
+        batch->fUseSRGBDistanceTable = useSRGBDistanceTable;
         batch->fFilteredColor = filteredColor;
         batch->fUseBGR = useBGR;
         batch->fBatch.fNumGlyphs = glyphCount;
@@ -182,6 +184,7 @@ private:
     // Distance field properties
     SkAutoTUnref<const GrDistanceFieldAdjustTable> fDistanceAdjustTable;
     SkColor fFilteredColor;
+    bool fUseSRGBDistanceTable;
 
     friend class GrBlobRegenHelper; // Needs to trigger flushes
 
index 0c4125d962083afe771dcb9ac94f4768c2f0dec7..e75466106541d61bb78066f06bf899785ff22ec3 100644 (file)
@@ -82,6 +82,7 @@ public:
         bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
                               kUniformScale_DistanceFieldEffectMask;
         bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
+        bool srgbOutput = SkToBool(dfTexEffect.getFlags() & kSRGB_DistanceFieldEffectFlag);
         varyingHandler->addVarying("TextureCoords", &uv, kHigh_GrSLPrecision);
         vertBuilder->codeAppendf("%s = %s;", uv.vsOut(), dfTexEffect.inTextureCoords()->fName);
 
@@ -153,7 +154,16 @@ public:
             // this gives us a smooth step across approximately one fragment
             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
         }
-        fragBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
+
+        // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
+        // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
+        // mapped linearly to coverage, so use a linear step:
+        if (srgbOutput) {
+            fragBuilder->codeAppend(
+                "float val = clamp(distance + afwidth / (2.0 * afwidth), 0.0, 1.0);");
+        } else {
+            fragBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
+        }
 
         fragBuilder->codeAppendf("%s = vec4(val);", args.fOutputCoverage);
     }
@@ -553,6 +563,7 @@ public:
         bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
                               kUniformScale_DistanceFieldEffectMask;
         bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
+        bool srgbOutput = SkToBool(dfTexEffect.getFlags() & kSRGB_DistanceFieldEffectFlag);
         GrGLSLVertToFrag recipScale(kFloat_GrSLType);
         GrGLSLVertToFrag uv(kVec2f_GrSLType);
         varyingHandler->addVarying("TextureCoords", &uv, kHigh_GrSLPrecision);
@@ -667,8 +678,17 @@ public:
             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
         }
 
-        fragBuilder->codeAppend(
-                      "vec4 val = vec4(smoothstep(vec3(-afwidth), vec3(afwidth), distance), 1.0);");
+        // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
+        // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
+        // mapped linearly to coverage, so use a linear step:
+        if (srgbOutput) {
+            fragBuilder->codeAppend("vec4 val = "
+                "vec4(clamp(distance + vec3(afwidth) / vec3(2.0 * afwidth), 0.0, 1.0), 1.0f);");
+        } else {
+            fragBuilder->codeAppend(
+                "vec4 val = vec4(smoothstep(vec3(-afwidth), vec3(afwidth), distance), 1.0);");
+        }
+
         // set alpha to be max of rgb coverage
         fragBuilder->codeAppend("val.a = max(max(val.r, val.g), val.b);");
 
index 05fffa58d62770f5cfb753dd086937cd75e20bf3..2786b7e823704b9bd361524190aa34f80bd7fd9d 100644 (file)
@@ -22,6 +22,7 @@ enum GrDistanceFieldEffectFlags {
     kUseLCD_DistanceFieldEffectFlag     = 0x04,   // use lcd text
     kBGR_DistanceFieldEffectFlag        = 0x08,   // lcd display has bgr order
     kPortrait_DistanceFieldEffectFlag   = 0x10,   // lcd display is in portrait mode (not used yet)
+    kSRGB_DistanceFieldEffectFlag       = 0x20,   // assume sRGB dest (use linstep, not smoothstep)
 
     kInvalid_DistanceFieldEffectFlag    = 0x80,   // invalid state (for initialization)
 
@@ -29,12 +30,14 @@ enum GrDistanceFieldEffectFlags {
                                             kScaleOnly_DistanceFieldEffectFlag,
     // The subset of the flags relevant to GrDistanceFieldA8TextGeoProc
     kNonLCD_DistanceFieldEffectMask       = kSimilarity_DistanceFieldEffectFlag |
-                                            kScaleOnly_DistanceFieldEffectFlag,
+                                            kScaleOnly_DistanceFieldEffectFlag |
+                                            kSRGB_DistanceFieldEffectFlag,
     // The subset of the flags relevant to GrDistanceFieldLCDTextGeoProc
     kLCD_DistanceFieldEffectMask          = kSimilarity_DistanceFieldEffectFlag |
                                             kScaleOnly_DistanceFieldEffectFlag |
                                             kUseLCD_DistanceFieldEffectFlag |
-                                            kBGR_DistanceFieldEffectFlag,
+                                            kBGR_DistanceFieldEffectFlag |
+                                            kSRGB_DistanceFieldEffectFlag,
 };
 
 /**
index afbff46994a30f7a175da8b1674ff06f25533902..60f905df5f2946657ad0f150f05197a43603fada 100644 (file)
@@ -257,6 +257,7 @@ inline GrDrawBatch* GrAtlasTextBlob::createBatch(
                                               GrColor color,
                                               const SkPaint& skPaint, const SkSurfaceProps& props,
                                               const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                              bool useSRGBDistanceTable,
                                               GrBatchFontCache* cache) {
     GrMaskFormat format = info.maskFormat();
     GrColor subRunColor;
@@ -278,8 +279,8 @@ inline GrDrawBatch* GrAtlasTextBlob::createBatch(
         }
         bool useBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
         batch = GrAtlasTextBatch::CreateDistanceField(glyphCount, cache,
-                                                      distanceAdjustTable, filteredColor,
-                                                      info.hasUseLCDText(), useBGR);
+                                                      distanceAdjustTable, useSRGBDistanceTable,
+                                                      filteredColor, info.hasUseLCDText(), useBGR);
     } else {
         batch = GrAtlasTextBatch::CreateBitmap(format, glyphCount, cache);
     }
@@ -310,10 +311,14 @@ void GrAtlasTextBlob::flushRun(GrDrawContext* dc, GrPipelineBuilder* pipelineBui
             continue;
         }
 
+        bool useSRGBDistanceTable = GrPixelConfigIsSRGB(dc->accessRenderTarget()->config()) &&
+                                    !pipelineBuilder->getDisableOutputConversionToSRGB();
+
         SkAutoTUnref<GrDrawBatch> batch(this->createBatch(info, glyphCount, run,
                                                           subRun, viewMatrix, x, y, color,
                                                           skPaint, props,
-                                                          distanceAdjustTable, cache));
+                                                          distanceAdjustTable, useSRGBDistanceTable,
+                                                          cache));
         dc->drawBatch(pipelineBuilder, batch);
     }
 }
@@ -463,7 +468,7 @@ GrDrawBatch* GrAtlasTextBlob::test_createBatch(
                                               GrBatchFontCache* cache) {
     const GrAtlasTextBlob::Run::SubRunInfo& info = fRuns[run].fSubRunInfo[subRun];
     return this->createBatch(info, glyphCount, run, subRun, viewMatrix, x, y, color, skPaint,
-                             props, distanceAdjustTable, cache);
+                             props, distanceAdjustTable, false, cache);
 }
 
 void GrAtlasTextBlob::AssertEqual(const GrAtlasTextBlob& l, const GrAtlasTextBlob& r) {
index bc1e0b4afc19269e77539d1ac68cbeb4e4e5b51f..9631ef1024a4f27ef289a661014edc36f7e2c0a2 100644 (file)
@@ -502,6 +502,7 @@ private:
                                     GrColor color,
                                     const SkPaint& skPaint, const SkSurfaceProps& props,
                                     const GrDistanceFieldAdjustTable* distanceAdjustTable,
+                                    bool useSRGBDistanceTable,
                                     GrBatchFontCache* cache);
 
     struct BigGlyph {
index 1c5aeceb80125a18159d3fad9eec87a93d1fcf24..c6da175e0b4df2da160661f00710fcb368709af2 100644 (file)
@@ -11,7 +11,7 @@
 
 SkDEBUGCODE(static const int kExpectedDistanceAdjustTableSize = 8;)
 
-void GrDistanceFieldAdjustTable::buildDistanceAdjustTable() {
+SkScalar* build_distance_adjust_table(SkScalar paintGamma, SkScalar deviceGamma) {
     // This is used for an approximation of the mask gamma hack, used by raster and bitmap
     // text. The mask gamma hack is based off of guessing what the blend color is going to
     // be, and adjusting the mask so that when run through the linear blend will
@@ -55,14 +55,12 @@ void GrDistanceFieldAdjustTable::buildDistanceAdjustTable() {
 #else
     SkScalar contrast = 0.5f;
 #endif
-    SkScalar paintGamma = SK_GAMMA_EXPONENT;
-    SkScalar deviceGamma = SK_GAMMA_EXPONENT;
 
     size = SkScalerContext::GetGammaLUTSize(contrast, paintGamma, deviceGamma,
         &width, &height);
 
     SkASSERT(kExpectedDistanceAdjustTableSize == height);
-    fTable = new SkScalar[height];
+    SkScalar* table = new SkScalar[height];
 
     SkAutoTArray<uint8_t> data((int)size);
     SkScalerContext::GetGammaLUTData(contrast, paintGamma, deviceGamma, data.get());
@@ -85,9 +83,16 @@ void GrDistanceFieldAdjustTable::buildDistanceAdjustTable() {
                 const float kDistanceFieldAAFactor = 0.65f; // should match SK_DistanceFieldAAFactor
                 float d = 2.0f*kDistanceFieldAAFactor*t - kDistanceFieldAAFactor;
 
-                fTable[row] = d;
+                table[row] = d;
                 break;
             }
         }
     }
+
+    return table;
+}
+
+void GrDistanceFieldAdjustTable::buildDistanceAdjustTables() {
+    fTable = build_distance_adjust_table(SK_GAMMA_EXPONENT, SK_GAMMA_EXPONENT);
+    fSRGBTable = build_distance_adjust_table(SK_Scalar1, SK_Scalar1);
 }
index f7d8bee0897a4fc37d602e9f7cefbacd125f0b23..f9b5161e95a7b3739d8aa7ae9161b592631820ea 100644 (file)
 // Because the GrAtlasTextContext can go out of scope before the final flush, this needs to be
 // refcnted and malloced
 struct GrDistanceFieldAdjustTable : public SkNVRefCnt<GrDistanceFieldAdjustTable> {
-    GrDistanceFieldAdjustTable() { this->buildDistanceAdjustTable(); }
-    ~GrDistanceFieldAdjustTable() { delete[] fTable; }
+    GrDistanceFieldAdjustTable() { this->buildDistanceAdjustTables(); }
+    ~GrDistanceFieldAdjustTable() {
+        delete[] fTable;
+        delete[] fSRGBTable;
+    }
 
-    const SkScalar& operator[] (int i) const {
-        return fTable[i];
+    const SkScalar& getAdjustment(int i, bool useSRGBTable) const {
+        return useSRGBTable ? fSRGBTable[i] : fTable[i];
     }
 
 private:
-    void buildDistanceAdjustTable();
+    void buildDistanceAdjustTables();
 
     SkScalar* fTable;
+    SkScalar* fSRGBTable;
 };
 
 #endif