blob: bfa2ed77df414d0e3d822695ac5657ab6d388b90 [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrNonlinearColorSpaceXformEffect.h"
#include "GrColorSpaceXform.h"
#include "GrProcessor.h"
#include "SkColorSpace_Base.h"
#include "glsl/GrGLSLFragmentProcessor.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
class GrGLNonlinearColorSpaceXformEffect : public GrGLSLFragmentProcessor {
public:
void emitCode(EmitArgs& args) override {
const GrNonlinearColorSpaceXformEffect& csxe =
args.fFp.cast<GrNonlinearColorSpaceXformEffect>();
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
const char* srcCoeffsName = nullptr;
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kSrcTransfer_Op)) {
fSrcTransferFnUni = uniformHandler->addUniformArray(
kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision,
"SrcTransferFn", GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
&srcCoeffsName);
}
const char* dstCoeffsName = nullptr;
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kDstTransfer_Op)) {
fDstTransferFnUni = uniformHandler->addUniformArray(
kFragment_GrShaderFlag, kFloat_GrSLType, kDefault_GrSLPrecision,
"DstTransferFn", GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
&dstCoeffsName);
}
const char* gamutXformName = nullptr;
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kGamutXform_Op)) {
fGamutXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kMat44f_GrSLType,
kDefault_GrSLPrecision, "GamutXform",
&gamutXformName);
}
// Helper function to apply the src or dst transfer function to a single value
SkString tfFuncNames[2];
for (size_t i = 0; i < 2; ++i) {
const char* coeffsName = i ? dstCoeffsName : srcCoeffsName;
if (!coeffsName) {
continue;
}
const char* fnName = i ? "dst_transfer_fn" : "src_transfer_fn";
static const GrShaderVar gTransferFnFuncArgs[] = {
GrShaderVar("x", kFloat_GrSLType),
};
SkString transferFnBody;
// Temporaries to make evaluation line readable
transferFnBody.printf("float A = %s[0];", coeffsName);
transferFnBody.appendf("float B = %s[1];", coeffsName);
transferFnBody.appendf("float C = %s[2];", coeffsName);
transferFnBody.appendf("float D = %s[3];", coeffsName);
transferFnBody.appendf("float E = %s[4];", coeffsName);
transferFnBody.appendf("float F = %s[5];", coeffsName);
transferFnBody.appendf("float G = %s[6];", coeffsName);
transferFnBody.appendf("return (x < D) ? (C * x) + F : pow(A * x + B, G) + E;");
fragBuilder->emitFunction(kFloat_GrSLType, fnName, SK_ARRAY_COUNT(gTransferFnFuncArgs),
gTransferFnFuncArgs, transferFnBody.c_str(), &tfFuncNames[i]);
}
if (nullptr == args.fInputColor) {
args.fInputColor = "vec4(1)";
}
fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
// 1: Un-premultiply the input color (if necessary)
fragBuilder->codeAppendf("float nonZeroAlpha = max(color.a, 0.00001);");
fragBuilder->codeAppendf("color = vec4(color.rgb / nonZeroAlpha, nonZeroAlpha);");
// 2: Apply src transfer function (to get to linear RGB)
if (srcCoeffsName) {
fragBuilder->codeAppendf("color.r = %s(color.r);", tfFuncNames[0].c_str());
fragBuilder->codeAppendf("color.g = %s(color.g);", tfFuncNames[0].c_str());
fragBuilder->codeAppendf("color.b = %s(color.b);", tfFuncNames[0].c_str());
}
// 3: Apply gamut matrix
if (gamutXformName) {
// Color is unpremultiplied at this point, so clamp to [0, 1]
fragBuilder->codeAppendf(
"color.rgb = clamp((%s * vec4(color.rgb, 1.0)).rgb, 0.0, 1.0);", gamutXformName);
}
// 4: Apply dst transfer fn
if (dstCoeffsName) {
fragBuilder->codeAppendf("color.r = %s(color.r);", tfFuncNames[1].c_str());
fragBuilder->codeAppendf("color.g = %s(color.g);", tfFuncNames[1].c_str());
fragBuilder->codeAppendf("color.b = %s(color.b);", tfFuncNames[1].c_str());
}
// 5: Premultiply again
fragBuilder->codeAppendf("%s = vec4(color.rgb * color.a, color.a);", args.fOutputColor);
}
static inline void GenKey(const GrProcessor& processor, const GrShaderCaps&,
GrProcessorKeyBuilder* b) {
const GrNonlinearColorSpaceXformEffect& csxe =
processor.cast<GrNonlinearColorSpaceXformEffect>();
b->add32(csxe.ops());
}
protected:
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& processor) override {
const GrNonlinearColorSpaceXformEffect& csxe =
processor.cast<GrNonlinearColorSpaceXformEffect>();
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kSrcTransfer_Op)) {
pdman.set1fv(fSrcTransferFnUni, GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
csxe.srcTransferFnCoeffs());
}
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kDstTransfer_Op)) {
pdman.set1fv(fDstTransferFnUni, GrNonlinearColorSpaceXformEffect::kNumTransferFnCoeffs,
csxe.dstTransferFnCoeffs());
}
if (SkToBool(csxe.ops() & GrNonlinearColorSpaceXformEffect::kGamutXform_Op)) {
pdman.setSkMatrix44(fGamutXformUni, csxe.gamutXform());
}
}
private:
GrGLSLProgramDataManager::UniformHandle fSrcTransferFnUni;
GrGLSLProgramDataManager::UniformHandle fDstTransferFnUni;
GrGLSLProgramDataManager::UniformHandle fGamutXformUni;
typedef GrGLSLFragmentProcessor INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
GrNonlinearColorSpaceXformEffect::GrNonlinearColorSpaceXformEffect(
uint32_t ops, const SkColorSpaceTransferFn& srcTransferFn,
const SkColorSpaceTransferFn& dstTransferFn, const SkMatrix44& gamutXform)
: INHERITED(kPreservesOpaqueInput_OptimizationFlag)
, fGamutXform(gamutXform)
, fOps(ops) {
this->initClassID<GrNonlinearColorSpaceXformEffect>();
fSrcTransferFnCoeffs[0] = srcTransferFn.fA;
fSrcTransferFnCoeffs[1] = srcTransferFn.fB;
fSrcTransferFnCoeffs[2] = srcTransferFn.fC;
fSrcTransferFnCoeffs[3] = srcTransferFn.fD;
fSrcTransferFnCoeffs[4] = srcTransferFn.fE;
fSrcTransferFnCoeffs[5] = srcTransferFn.fF;
fSrcTransferFnCoeffs[6] = srcTransferFn.fG;
fDstTransferFnCoeffs[0] = dstTransferFn.fA;
fDstTransferFnCoeffs[1] = dstTransferFn.fB;
fDstTransferFnCoeffs[2] = dstTransferFn.fC;
fDstTransferFnCoeffs[3] = dstTransferFn.fD;
fDstTransferFnCoeffs[4] = dstTransferFn.fE;
fDstTransferFnCoeffs[5] = dstTransferFn.fF;
fDstTransferFnCoeffs[6] = dstTransferFn.fG;
}
bool GrNonlinearColorSpaceXformEffect::onIsEqual(const GrFragmentProcessor& s) const {
const GrNonlinearColorSpaceXformEffect& other = s.cast<GrNonlinearColorSpaceXformEffect>();
if (other.fOps != fOps) {
return false;
}
if (SkToBool(fOps & kSrcTransfer_Op) &&
memcmp(&other.fSrcTransferFnCoeffs, &fSrcTransferFnCoeffs, sizeof(fSrcTransferFnCoeffs))) {
return false;
}
if (SkToBool(fOps & kDstTransfer_Op) &&
memcmp(&other.fDstTransferFnCoeffs, &fDstTransferFnCoeffs, sizeof(fDstTransferFnCoeffs))) {
return false;
}
if (SkToBool(fOps & kGamutXform_Op) && other.fGamutXform != fGamutXform) {
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrNonlinearColorSpaceXformEffect);
#if GR_TEST_UTILS
sk_sp<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::TestCreate(GrProcessorTestData* d) {
// TODO: Generate a random variety of color spaces for this effect (it can handle wacky
// transfer functions, etc...)
sk_sp<SkColorSpace> srcSpace = SkColorSpace::MakeSRGBLinear();
sk_sp<SkColorSpace> dstSpace = SkColorSpace::MakeSRGB();
return GrNonlinearColorSpaceXformEffect::Make(srcSpace.get(), dstSpace.get());
}
#endif
///////////////////////////////////////////////////////////////////////////////
void GrNonlinearColorSpaceXformEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
GrProcessorKeyBuilder* b) const {
GrGLNonlinearColorSpaceXformEffect::GenKey(*this, caps, b);
}
GrGLSLFragmentProcessor* GrNonlinearColorSpaceXformEffect::onCreateGLSLInstance() const {
return new GrGLNonlinearColorSpaceXformEffect();
}
sk_sp<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::Make(const SkColorSpace* src,
const SkColorSpace* dst) {
if (!src || !dst || SkColorSpace::Equals(src, dst)) {
// No conversion possible (or necessary)
return nullptr;
}
uint32_t ops = 0;
// We rely on GrColorSpaceXform to build the gamut xform matrix for us (to get caching)
auto gamutXform = GrColorSpaceXform::Make(src, dst);
SkMatrix44 srcToDstMtx(SkMatrix44::kUninitialized_Constructor);
if (gamutXform) {
ops |= kGamutXform_Op;
srcToDstMtx = gamutXform->srcToDst();
}
SkColorSpaceTransferFn srcTransferFn;
if (!src->gammaIsLinear()) {
if (src->isNumericalTransferFn(&srcTransferFn)) {
ops |= kSrcTransfer_Op;
} else {
return nullptr;
}
}
SkColorSpaceTransferFn dstTransferFn;
if (!dst->gammaIsLinear()) {
if (dst->isNumericalTransferFn(&dstTransferFn)) {
dstTransferFn = dstTransferFn.invert();
ops |= kDstTransfer_Op;
} else {
return nullptr;
}
}
return sk_sp<GrFragmentProcessor>(new GrNonlinearColorSpaceXformEffect(
ops, srcTransferFn, dstTransferFn, srcToDstMtx));
}