Do color correction of vertex colors in GPU drawVertices
SkColor vertex colors need to be linearized (from sRGB),
and possibly converted from sRGB gamut to destination gamut.
Bug: skia:6659
Change-Id: I2b1b1dd0fa5938519693f56a728fed5957f13fd5
Reviewed-on: https://skia-review.googlesource.com/17534
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 90d3239..3f50bea 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -8,6 +8,7 @@
#include "GrDefaultGeoProcFactory.h"
#include "SkRefCnt.h"
+#include "glsl/GrGLSLColorSpaceXformHelper.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "glsl/GrGLSLVertexShaderBuilder.h"
@@ -26,18 +27,22 @@
kColorAttributeIsSkColor_GPFlag = 0x2,
kLocalCoordAttribute_GPFlag = 0x4,
kCoverageAttribute_GPFlag = 0x8,
+
+ kLinearizeColorAttribute_GPFlag = 0x10,
};
class DefaultGeoProc : public GrGeometryProcessor {
public:
static sk_sp<GrGeometryProcessor> Make(uint32_t gpTypeFlags,
GrColor color,
+ sk_sp<GrColorSpaceXform> colorSpaceXform,
const SkMatrix& viewMatrix,
const SkMatrix& localMatrix,
bool localCoordsWillBeRead,
uint8_t coverage) {
return sk_sp<GrGeometryProcessor>(new DefaultGeoProc(
- gpTypeFlags, color, viewMatrix, localMatrix, coverage, localCoordsWillBeRead));
+ gpTypeFlags, color, std::move(colorSpaceXform), viewMatrix, localMatrix, coverage,
+ localCoordsWillBeRead));
}
const char* name() const override { return "DefaultGeometryProcessor"; }
@@ -53,6 +58,12 @@
bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
uint8_t coverage() const { return fCoverage; }
bool hasVertexCoverage() const { return SkToBool(fInCoverage); }
+ bool linearizeColor() const {
+ // Linearization should only happen with SkColor
+ bool linearize = SkToBool(fFlags & kLinearizeColorAttribute_GPFlag);
+ SkASSERT(!linearize || (fFlags & kColorAttributeIsSkColor_GPFlag));
+ return linearize;
+ }
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
@@ -73,14 +84,47 @@
if (gp.hasVertexColor()) {
GrGLSLVertToFrag varying(kVec4f_GrSLType);
varyingHandler->addVarying("color", &varying);
- if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
- // Do a red/blue swap and premul the color.
- vertBuilder->codeAppendf("%s = vec4(%s.a*%s.bgr, %s.a);", varying.vsOut(),
- gp.inColor()->fName, gp.inColor()->fName,
+
+ // There are several optional steps to process the color. Start with the attribute:
+ vertBuilder->codeAppendf("vec4 color = %s;", gp.inColor()->fName);
+
+ // Linearize
+ if (gp.linearizeColor()) {
+ SkString srgbFuncName;
+ static const GrShaderVar gSrgbArgs[] = {
+ GrShaderVar("x", kFloat_GrSLType),
+ };
+ vertBuilder->emitFunction(kFloat_GrSLType,
+ "srgb_to_linear",
+ SK_ARRAY_COUNT(gSrgbArgs),
+ gSrgbArgs,
+ "return (x <= 0.04045) ? (x / 12.92) "
+ ": pow((x + 0.055) / 1.055, 2.4);",
+ &srgbFuncName);
+ vertBuilder->codeAppendf("color = vec4(%s(%s.r), %s(%s.g), %s(%s.b), %s.a);",
+ srgbFuncName.c_str(), gp.inColor()->fName,
+ srgbFuncName.c_str(), gp.inColor()->fName,
+ srgbFuncName.c_str(), gp.inColor()->fName,
gp.inColor()->fName);
- } else {
- vertBuilder->codeAppendf("%s = %s;\n", varying.vsOut(), gp.inColor()->fName);
}
+
+ // For SkColor, do a red/blue swap and premul
+ if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
+ vertBuilder->codeAppend("color = vec4(color.a * color.bgr, color.a);");
+ }
+
+ // Do color-correction to destination gamut
+ if (gp.linearizeColor()) {
+ fColorSpaceHelper.emitCode(uniformHandler, gp.fColorSpaceXform.get(),
+ kVertex_GrShaderFlag);
+ if (fColorSpaceHelper.isValid()) {
+ SkString xformedColor;
+ vertBuilder->appendColorGamutXform(&xformedColor, "color",
+ &fColorSpaceHelper);
+ vertBuilder->codeAppendf("color = %s;", xformedColor.c_str());
+ }
+ }
+ vertBuilder->codeAppendf("%s = color;\n", varying.vsOut());
fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn());
} else {
this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
@@ -142,6 +186,9 @@
key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x20 : 0x0;
key |= ComputePosKey(def.viewMatrix()) << 20;
b->add32(key);
+ if (def.linearizeColor()) {
+ b->add32(GrColorSpaceXform::XformKey(def.fColorSpaceXform.get()));
+ }
}
void setData(const GrGLSLProgramDataManager& pdman,
@@ -168,6 +215,10 @@
fCoverage = dgp.coverage();
}
this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter);
+
+ if (dgp.linearizeColor() && dgp.fColorSpaceXform) {
+ fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get());
+ }
}
private:
@@ -177,6 +228,7 @@
UniformHandle fViewMatrixUniform;
UniformHandle fColorUniform;
UniformHandle fCoverageUniform;
+ GrGLSLColorSpaceXformHelper fColorSpaceHelper;
typedef GrGLSLGeometryProcessor INHERITED;
};
@@ -192,6 +244,7 @@
private:
DefaultGeoProc(uint32_t gpTypeFlags,
GrColor color,
+ sk_sp<GrColorSpaceXform> colorSpaceXform,
const SkMatrix& viewMatrix,
const SkMatrix& localMatrix,
uint8_t coverage,
@@ -201,7 +254,8 @@
, fLocalMatrix(localMatrix)
, fCoverage(coverage)
, fFlags(gpTypeFlags)
- , fLocalCoordsWillBeRead(localCoordsWillBeRead) {
+ , fLocalCoordsWillBeRead(localCoordsWillBeRead)
+ , fColorSpaceXform(std::move(colorSpaceXform)) {
this->initClassID<DefaultGeoProc>();
fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
@@ -228,6 +282,7 @@
uint8_t fCoverage;
uint32_t fFlags;
bool fLocalCoordsWillBeRead;
+ sk_sp<GrColorSpaceXform> fColorSpaceXform;
GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
@@ -254,9 +309,9 @@
return DefaultGeoProc::Make(flags,
GrRandomColor(d->fRandom),
+ GrTest::TestColorXform(d->fRandom),
GrTest::TestMatrix(d->fRandom),
GrTest::TestMatrix(d->fRandom),
-
d->fRandom->nextBool(),
GrRandomCoverage(d->fRandom));
}
@@ -272,6 +327,12 @@
} else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
}
+ if (color.fLinearize) {
+ // It only makes sense to linearize SkColors (which are always sRGB). GrColor values should
+ // have been linearized and gamut-converted during paint conversion
+ SkASSERT(Color::kUnpremulSkColorAttribute_Type == color.fType);
+ flags |= kLinearizeColorAttribute_GPFlag;
+ }
flags |= coverage.fType == Coverage::kAttribute_Type ? kCoverageAttribute_GPFlag : 0;
flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
@@ -281,6 +342,7 @@
GrColor inColor = color.fColor;
return DefaultGeoProc::Make(flags,
inColor,
+ color.fColorSpaceXform,
viewMatrix,
localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
localCoordsWillBeRead,
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 00ee90d..6a3c0b7 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -66,13 +66,26 @@
kPremulGrColorAttribute_Type,
kUnpremulSkColorAttribute_Type,
};
- explicit Color(GrColor color) : fType(kPremulGrColorUniform_Type), fColor(color) {}
- Color(Type type) : fType(type), fColor(GrColor_ILLEGAL) {
+ explicit Color(GrColor color)
+ : fType(kPremulGrColorUniform_Type)
+ , fColor(color)
+ , fLinearize(false)
+ , fColorSpaceXform(nullptr) {}
+ Color(Type type)
+ : fType(type)
+ , fColor(GrColor_ILLEGAL)
+ , fLinearize(false)
+ , fColorSpaceXform(nullptr) {
SkASSERT(type != kPremulGrColorUniform_Type);
}
Type fType;
GrColor fColor;
+
+ // These options only apply to SkColor. Any GrColors are assumed to have been color managed
+ // during paint conversion.
+ bool fLinearize;
+ sk_sp<GrColorSpaceXform> fColorSpaceXform;
};
struct Coverage {
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 7d18448..1574fc5 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -848,6 +848,8 @@
SkASSERT(vertices);
std::unique_ptr<GrLegacyMeshDrawOp> op = GrDrawVerticesOp::Make(paint.getColor(),
std::move(vertices), viewMatrix,
+ this->isGammaCorrect(),
+ fColorXformFromSRGB,
overridePrimType);
if (!op) {
return;
diff --git a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
index 1571b06..9653efb 100644
--- a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
+++ b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
@@ -20,10 +20,11 @@
public:
GrGLSLColorSpaceXformHelper() : fValid(false) {}
- void emitCode(GrGLSLUniformHandler* uniformHandler, GrColorSpaceXform* colorSpaceXform) {
+ void emitCode(GrGLSLUniformHandler* uniformHandler, GrColorSpaceXform* colorSpaceXform,
+ uint32_t visibility = kFragment_GrShaderFlag) {
SkASSERT(uniformHandler);
if (colorSpaceXform) {
- fGamutXformVar = uniformHandler->addUniform(kFragment_GrShaderFlag, kMat44f_GrSLType,
+ fGamutXformVar = uniformHandler->addUniform(visibility, kMat44f_GrSLType,
kDefault_GrSLPrecision, "ColorXform");
fValid = true;
}
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 1e04c68..e5b1383 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -13,24 +13,34 @@
std::unique_ptr<GrLegacyMeshDrawOp> GrDrawVerticesOp::Make(GrColor color,
sk_sp<SkVertices> vertices,
const SkMatrix& viewMatrix,
+ bool gammaCorrect,
+ sk_sp<GrColorSpaceXform> colorSpaceXform,
GrPrimitiveType* overridePrimType) {
SkASSERT(vertices);
GrPrimitiveType primType = overridePrimType ? *overridePrimType
: SkVertexModeToGrPrimitiveType(vertices->mode());
- return std::unique_ptr<GrLegacyMeshDrawOp>(
- new GrDrawVerticesOp(std::move(vertices), primType, color, viewMatrix));
+ return std::unique_ptr<GrLegacyMeshDrawOp>(new GrDrawVerticesOp(std::move(vertices), primType,
+ color, gammaCorrect,
+ std::move(colorSpaceXform),
+ viewMatrix));
}
GrDrawVerticesOp::GrDrawVerticesOp(sk_sp<SkVertices> vertices, GrPrimitiveType primitiveType,
- GrColor color, const SkMatrix& viewMatrix)
- : INHERITED(ClassID()) {
+ GrColor color, bool gammaCorrect,
+ sk_sp<GrColorSpaceXform> colorSpaceXform,
+ const SkMatrix& viewMatrix)
+ : INHERITED(ClassID())
+ , fPrimitiveType(primitiveType)
+ , fColorSpaceXform(std::move(colorSpaceXform)) {
SkASSERT(vertices);
fVertexCount = vertices->vertexCount();
fIndexCount = vertices->indexCount();
fColorArrayType = vertices->hasColors() ? ColorArrayType::kSkColor
: ColorArrayType::kPremulGrColor;
- fPrimitiveType = primitiveType;
+ // GrColor is linearized (and gamut converted) during paint conversion, but SkColors need to be
+ // handled in the shader
+ fLinearizeColors = gammaCorrect && vertices->hasColors();
Mesh& mesh = fMeshes.push_back();
mesh.fColor = color;
@@ -74,6 +84,7 @@
fMeshes[0].fIgnoreColors = true;
fFlags &= ~kRequiresPerVertexColors_Flag;
fColorArrayType = ColorArrayType::kPremulGrColor;
+ fLinearizeColors = false;
}
if (optimizations.readsLocalCoords()) {
fFlags |= kPipelineRequiresLocalCoords_Flag;
@@ -107,6 +118,8 @@
color.fType = (fColorArrayType == ColorArrayType::kPremulGrColor)
? Color::kPremulGrColorAttribute_Type
: Color::kUnpremulSkColorAttribute_Type;
+ color.fLinearize = fLinearizeColors;
+ color.fColorSpaceXform = fColorSpaceXform;
*hasColorAttribute = true;
} else {
*hasColorAttribute = false;
@@ -246,10 +259,18 @@
return false;
}
+ if (fLinearizeColors != that->fLinearizeColors) {
+ return false;
+ }
+
if (fVertexCount + that->fVertexCount > SK_MaxU16) {
return false;
}
+ // NOTE: For SkColor vertex colors, the source color space is always sRGB, and the destination
+ // gamut is determined by the render target context. A mis-match should be impossible.
+ SkASSERT(GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get()));
+
// If either op required explicit local coords or per-vertex colors the combined mesh does. Same
// with multiple view matrices.
fFlags |= that->fFlags;
@@ -349,6 +370,7 @@
bool hasTexCoords = random->nextBool();
bool hasIndices = random->nextBool();
bool hasColors = random->nextBool();
+ bool linearizeColors = random->nextBool();
uint32_t vertexCount = seed_vertices(type) + (primitiveCount - 1) * primitive_vertices(type);
@@ -367,6 +389,7 @@
SkMatrix viewMatrix = GrTest::TestMatrix(random);
GrColor color = GrRandomColor(random);
+ sk_sp<GrColorSpaceXform> colorSpaceXform = GrTest::TestColorXform(random);
static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode;
sk_sp<SkVertices> vertices = SkVertices::MakeCopy(kIgnoredMode, vertexCount, positions.begin(),
@@ -374,7 +397,8 @@
hasIndices ? indices.count() : 0,
indices.begin());
return std::unique_ptr<GrLegacyMeshDrawOp>(
- new GrDrawVerticesOp(std::move(vertices), type, color, viewMatrix));
+ new GrDrawVerticesOp(std::move(vertices), type, color, linearizeColors,
+ std::move(colorSpaceXform), viewMatrix));
}
#endif
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index 0c75dcf..1d788ab 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -33,9 +33,12 @@
* Draw a SkVertices. The GrColor param is used if the vertices lack per-vertex color. If the
* vertices lack local coords then the vertex positions are used as local coords. The primitive
* type drawn is derived from the SkVertices object, unless overridePrimType is specified.
+ * If gammaCorrect is true, the vertex colors will be linearized in the shader to get correct
+ * rendering.
*/
static std::unique_ptr<GrLegacyMeshDrawOp> Make(GrColor color, sk_sp<SkVertices>,
- const SkMatrix& viewMatrix,
+ const SkMatrix& viewMatrix, bool gammaCorrect,
+ sk_sp<GrColorSpaceXform> colorSpaceXform,
GrPrimitiveType* overridePrimType = nullptr);
const char* name() const override { return "DrawVerticesOp"; }
@@ -55,7 +58,8 @@
kSkColor,
};
- GrDrawVerticesOp(sk_sp<SkVertices>, GrPrimitiveType, GrColor, const SkMatrix& viewMatrix);
+ GrDrawVerticesOp(sk_sp<SkVertices>, GrPrimitiveType, GrColor, bool gammaCorrect,
+ sk_sp<GrColorSpaceXform>, const SkMatrix& viewMatrix);
void getProcessorAnalysisInputs(GrProcessorAnalysisColor* color,
GrProcessorAnalysisCoverage* coverage) const override;
@@ -125,6 +129,8 @@
int fVertexCount;
int fIndexCount;
ColorArrayType fColorArrayType;
+ bool fLinearizeColors;
+ sk_sp<GrColorSpaceXform> fColorSpaceXform;
SkSTArray<1, Mesh, true> fMeshes;
typedef GrLegacyMeshDrawOp INHERITED;