Starting to hack up HDR transfer function support
Brings over skcms' encoding scheme, etc.
Change-Id: Ib8abec911acd1c50df3b201b4a9bde01b1cb123b
Bug: chromium:960620
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/249000
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index bac4b88..4e77d7c 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -38,7 +38,7 @@
sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
const skcms_Matrix3x3& toXYZ) {
- if (!is_valid_transfer_fn(transferFn)) {
+ if (classify_transfer_fn(transferFn) == Bad_TF) {
return nullptr;
}
@@ -116,8 +116,11 @@
}
bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
+ // TODO: Change transferFn/invTransferFn to just operate on skcms_TransferFunction (all callers
+ // already pass pointers to an skcms struct). Then remove this function, and update the two
+ // remaining callers to do the right thing with transferFn and classify.
this->transferFn(&coeffs->g);
- return true;
+ return classify_transfer_fn(*coeffs) == sRGBish_TF;
}
void SkColorSpace::transferFn(float gabcdef[7]) const {
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 97d52e9..425357d 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -31,54 +31,37 @@
return SkTAbs(a - b) < 0.001f;
}
-static inline bool is_valid_transfer_fn(const skcms_TransferFunction& coeffs) {
- if (SkScalarIsNaN(coeffs.a) || SkScalarIsNaN(coeffs.b) ||
- SkScalarIsNaN(coeffs.c) || SkScalarIsNaN(coeffs.d) ||
- SkScalarIsNaN(coeffs.e) || SkScalarIsNaN(coeffs.f) ||
- SkScalarIsNaN(coeffs.g))
- {
- return false;
- }
+// NOTE: All of this logic is copied from skcms.cc, and needs to be kept in sync.
- if (coeffs.d < 0.0f) {
- return false;
- }
+// Most transfer functions we work with are sRGBish.
+// For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
+// and repurpose the other fields to hold the parameters of the HDR functions.
+enum TFKind { Bad_TF, sRGBish_TF, PQish_TF, HLGish_TF, HLGinvish_TF };
- if (coeffs.d == 0.0f) {
- // Y = (aX + b)^g + e for always
- if (0.0f == coeffs.a || 0.0f == coeffs.g) {
- SkColorSpacePrintf("A or G is zero, constant transfer function "
- "is nonsense");
- return false;
+static inline TFKind classify_transfer_fn(const skcms_TransferFunction& tf) {
+ if (tf.g < 0 && (int)tf.g == tf.g) {
+ // TODO: sanity checks for PQ/HLG like we do for sRGBish.
+ switch (-(int)tf.g) {
+ case PQish_TF: return PQish_TF;
+ case HLGish_TF: return HLGish_TF;
+ case HLGinvish_TF: return HLGinvish_TF;
}
+ return Bad_TF;
}
- if (coeffs.d >= 1.0f) {
- // Y = cX + f for always
- if (0.0f == coeffs.c) {
- SkColorSpacePrintf("C is zero, constant transfer function is "
- "nonsense");
- return false;
- }
+ // Basic sanity checks for sRGBish transfer functions.
+ if (sk_float_isfinite(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
+ // a,c,d,g should be non-negative to make any sense.
+ && tf.a >= 0
+ && tf.c >= 0
+ && tf.d >= 0
+ && tf.g >= 0
+ // Raising a negative value to a fractional tf->g produces complex numbers.
+ && tf.a * tf.d + tf.b >= 0) {
+ return sRGBish_TF;
}
- if ((0.0f == coeffs.a || 0.0f == coeffs.g) && 0.0f == coeffs.c) {
- SkColorSpacePrintf("A or G, and C are zero, constant transfer function "
- "is nonsense");
- return false;
- }
-
- if (coeffs.c < 0.0f) {
- SkColorSpacePrintf("Transfer function must be increasing");
- return false;
- }
-
- if (coeffs.a < 0.0f || coeffs.g < 0.0f) {
- SkColorSpacePrintf("Transfer function must be positive or increasing");
- return false;
- }
-
- return true;
+ return Bad_TF;
}
static inline bool is_almost_srgb(const skcms_TransferFunction& coeffs) {
diff --git a/src/core/SkColorSpaceXformSteps.cpp b/src/core/SkColorSpaceXformSteps.cpp
index 9c45121..fe03bca 100644
--- a/src/core/SkColorSpaceXformSteps.cpp
+++ b/src/core/SkColorSpaceXformSteps.cpp
@@ -114,12 +114,9 @@
rgba[2] *= invA;
}
if (flags.linearize) {
- skcms_TransferFunction tf;
- memcpy(&tf, &srcTF, 7*sizeof(float));
-
- rgba[0] = skcms_TransferFunction_eval(&tf, rgba[0]);
- rgba[1] = skcms_TransferFunction_eval(&tf, rgba[1]);
- rgba[2] = skcms_TransferFunction_eval(&tf, rgba[2]);
+ rgba[0] = skcms_TransferFunction_eval(&srcTF, rgba[0]);
+ rgba[1] = skcms_TransferFunction_eval(&srcTF, rgba[1]);
+ rgba[2] = skcms_TransferFunction_eval(&srcTF, rgba[2]);
}
if (flags.gamut_transform) {
float temp[3] = { rgba[0], rgba[1], rgba[2] };
@@ -130,12 +127,9 @@
}
}
if (flags.encode) {
- skcms_TransferFunction tf;
- memcpy(&tf, &dstTFInv, 7*sizeof(float));
-
- rgba[0] = skcms_TransferFunction_eval(&tf, rgba[0]);
- rgba[1] = skcms_TransferFunction_eval(&tf, rgba[1]);
- rgba[2] = skcms_TransferFunction_eval(&tf, rgba[2]);
+ rgba[0] = skcms_TransferFunction_eval(&dstTFInv, rgba[0]);
+ rgba[1] = skcms_TransferFunction_eval(&dstTFInv, rgba[1]);
+ rgba[2] = skcms_TransferFunction_eval(&dstTFInv, rgba[2]);
}
if (flags.premul) {
rgba[0] *= rgba[3];
@@ -152,15 +146,8 @@
if (flags.linearize) {
if (src_is_normalized && srcTF_is_sRGB) {
p->append(SkRasterPipeline::from_srgb);
- } else if (srcTF.a == 1 &&
- srcTF.b == 0 &&
- srcTF.c == 0 &&
- srcTF.d == 0 &&
- srcTF.e == 0 &&
- srcTF.f == 0) {
- p->append(SkRasterPipeline::gamma_, &srcTF.g);
} else {
- p->append(SkRasterPipeline::parametric, &srcTF);
+ p->append_transfer_function(srcTF);
}
}
if (flags.gamut_transform) {
@@ -169,15 +156,8 @@
if (flags.encode) {
if (src_is_normalized && dstTF_is_sRGB) {
p->append(SkRasterPipeline::to_srgb);
- } else if (dstTFInv.a == 1 &&
- dstTFInv.b == 0 &&
- dstTFInv.c == 0 &&
- dstTFInv.d == 0 &&
- dstTFInv.e == 0 &&
- dstTFInv.f == 0) {
- p->append(SkRasterPipeline::gamma_, &dstTFInv.g);
} else {
- p->append(SkRasterPipeline::parametric, &dstTFInv);
+ p->append_transfer_function(dstTFInv);
}
}
if (flags.premul) { p->append(SkRasterPipeline::premul); }
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index 2a2d413..78e4a90 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -293,7 +293,8 @@
sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn,
const skcms_Matrix3x3& toXYZD50) {
- if (!is_valid_transfer_fn(fn)) {
+ // We can't encode HDR transfer functions in ICC
+ if (classify_transfer_fn(fn) != sRGBish_TF) {
return nullptr;
}
diff --git a/src/core/SkRasterPipeline.cpp b/src/core/SkRasterPipeline.cpp
index 2e26b47..4f73643 100644
--- a/src/core/SkRasterPipeline.cpp
+++ b/src/core/SkRasterPipeline.cpp
@@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
+#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkOpts.h"
#include "src/core/SkRasterPipeline.h"
#include <algorithm>
@@ -24,6 +25,11 @@
SkASSERT(stage != set_rgb); // Please use append_set_rgb().
SkASSERT(stage != unbounded_set_rgb); // Please use append_set_rgb().
SkASSERT(stage != clamp_gamut); // Please use append_gamut_clamp_if_normalized().
+ SkASSERT(stage != parametric); // Please use append_transfer_function().
+ SkASSERT(stage != gamma_); // Please use append_transfer_function().
+ SkASSERT(stage != PQish); // Please use append_transfer_function().
+ SkASSERT(stage != HLGish); // Please use append_transfer_function().
+ SkASSERT(stage != HLGinvish); // Please use append_transfer_function().
this->unchecked_append(stage, ctx);
}
void SkRasterPipeline::unchecked_append(StockStage stage, void* ctx) {
@@ -267,6 +273,24 @@
}
}
+void SkRasterPipeline::append_transfer_function(const skcms_TransferFunction& tf) {
+ void* ctx = const_cast<void*>(static_cast<const void*>(&tf));
+ switch (classify_transfer_fn(tf)) {
+ case Bad_TF: SkASSERT(false); break;
+
+ case TFKind::sRGBish_TF:
+ if (tf.a == 1 && tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0) {
+ this->unchecked_append(gamma_, ctx);
+ } else {
+ this->unchecked_append(parametric, ctx);
+ }
+ break;
+ case PQish_TF: this->unchecked_append(PQish, ctx); break;
+ case HLGish_TF: this->unchecked_append(HLGish, ctx); break;
+ case HLGinvish_TF: this->unchecked_append(HLGinvish, ctx); break;
+ }
+}
+
void SkRasterPipeline::append_gamut_clamp_if_normalized(const SkImageInfo& dstInfo) {
// N.B. we _do_ clamp for kRGBA_F16Norm_SkColorType... because it's normalized.
if (dstInfo.colorType() != kRGBA_F16_SkColorType &&
diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h
index fab9540..d13a8f0 100644
--- a/src/core/SkRasterPipeline.h
+++ b/src/core/SkRasterPipeline.h
@@ -74,7 +74,7 @@
M(matrix_translate) M(matrix_scale_translate) \
M(matrix_2x3) M(matrix_3x3) M(matrix_3x4) M(matrix_4x5) M(matrix_4x3) \
M(matrix_perspective) \
- M(parametric) M(gamma_) \
+ M(parametric) M(gamma_) M(PQish) M(HLGish) M(HLGinvish) \
M(mirror_x) M(repeat_x) \
M(mirror_y) M(repeat_y) \
M(decal_x) M(decal_y) M(decal_x_and_y) \
@@ -261,6 +261,8 @@
void append_gamut_clamp_if_normalized(const SkImageInfo&);
+ void append_transfer_function(const skcms_TransferFunction&);
+
bool empty() const { return fStages == nullptr; }
private:
diff --git a/src/effects/SkHighContrastFilter.cpp b/src/effects/SkHighContrastFilter.cpp
index 8a5263f..5382a03 100644
--- a/src/effects/SkHighContrastFilter.cpp
+++ b/src/effects/SkHighContrastFilter.cpp
@@ -72,7 +72,7 @@
// TODO: sRGB?
*tf = {2,1, 0,0,0,0,0};
}
- p->append(SkRasterPipeline::parametric, tf);
+ p->append_transfer_function(*tf);
if (fConfig.fGrayscale) {
float r = SK_LUM_COEFF_R;
@@ -120,7 +120,7 @@
// See above... historically untagged == gamma 2 in this filter.
*invTF ={0.5f,1, 0,0,0,0,0};
}
- p->append(SkRasterPipeline::parametric, invTF);
+ p->append_transfer_function(*invTF);
if (!shaderIsOpaque) {
p->append(SkRasterPipeline::premul);
diff --git a/src/gpu/GrColorSpaceXform.h b/src/gpu/GrColorSpaceXform.h
index 6abcef1..65e1836 100644
--- a/src/gpu/GrColorSpaceXform.h
+++ b/src/gpu/GrColorSpaceXform.h
@@ -9,6 +9,7 @@
#define GrColorSpaceXform_DEFINED
#include "include/core/SkRefCnt.h"
+#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/gpu/GrFragmentProcessor.h"
@@ -31,8 +32,19 @@
* computed key.
*/
static uint32_t XformKey(const GrColorSpaceXform* xform) {
- // Code generation depends on which steps we apply
- return xform ? xform->fSteps.flags.mask() : 0;
+ // Code generation depends on which steps we apply,
+ // and the kinds of transfer functions (if we're applying those).
+ if (!xform) { return 0; }
+
+ const SkColorSpaceXformSteps& steps(xform->fSteps);
+ uint32_t key = steps.flags.mask();
+ if (steps.flags.linearize) {
+ key |= classify_transfer_fn(steps.srcTF) << 8;
+ }
+ if (steps.flags.encode) {
+ key |= classify_transfer_fn(steps.dstTFInv) << 16;
+ }
+ return key;
}
static bool Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b);
diff --git a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
index 52a9eda..1e68897 100644
--- a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
+++ b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
@@ -8,6 +8,7 @@
#ifndef GrGLSLColorSpaceXformHelper_DEFINED
#define GrGLSLColorSpaceXformHelper_DEFINED
+#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkColorSpaceXformSteps.h"
#include "src/gpu/GrColorSpaceXform.h"
#include "src/gpu/glsl/GrGLSLUniformHandler.h"
@@ -31,6 +32,7 @@
if (this->applySrcTF()) {
fSrcTFVar = uniformHandler->addUniformArray(visibility, kHalf_GrSLType,
"SrcTF", kNumTransferFnCoeffs);
+ fSrcTFKind = classify_transfer_fn(colorSpaceXform->fSteps.srcTF);
}
if (this->applyGamutXform()) {
fGamutXformVar = uniformHandler->addUniform(visibility, kHalf3x3_GrSLType,
@@ -39,6 +41,7 @@
if (this->applyDstTF()) {
fDstTFVar = uniformHandler->addUniformArray(visibility, kHalf_GrSLType,
"DstTF", kNumTransferFnCoeffs);
+ fDstTFKind = classify_transfer_fn(colorSpaceXform->fSteps.dstTFInv);
}
}
}
@@ -63,6 +66,9 @@
bool applyDstTF() const { return fFlags.encode; }
bool applyPremul() const { return fFlags.premul; }
+ TFKind srcTFKind() const { return fSrcTFKind; }
+ TFKind dstTFKind() const { return fDstTFKind; }
+
GrGLSLProgramDataManager::UniformHandle srcTFUniform() const { return fSrcTFVar; }
GrGLSLProgramDataManager::UniformHandle gamutXformUniform() const { return fGamutXformVar; }
GrGLSLProgramDataManager::UniformHandle dstTFUniform() const { return fDstTFVar; }
@@ -74,6 +80,8 @@
GrGLSLProgramDataManager::UniformHandle fGamutXformVar;
GrGLSLProgramDataManager::UniformHandle fDstTFVar;
SkColorSpaceXformSteps::Flags fFlags;
+ TFKind fSrcTFKind;
+ TFKind fDstTFKind;
};
#endif
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index a87d591..7675744 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -112,11 +112,13 @@
// function, one for the (inverse) destination transfer function, and one for the gamut xform.
// Any combination of these may be present, although some configurations are much more likely.
- auto emitTFFunc = [=](const char* name, GrGLSLProgramDataManager::UniformHandle uniform) {
+ auto emitTFFunc = [=](const char* name, GrGLSLProgramDataManager::UniformHandle uniform,
+ TFKind kind) {
const GrShaderVar gTFArgs[] = { GrShaderVar("x", kHalf_GrSLType) };
const char* coeffs = uniformHandler->getUniformCStr(uniform);
SkString body;
- // Temporaries to make evaluation line readable
+ // Temporaries to make evaluation line readable. We always use the sRGBish names, so the
+ // PQ and HLG math is confusing.
body.appendf("half G = %s[0];", coeffs);
body.appendf("half A = %s[1];", coeffs);
body.appendf("half B = %s[2];", coeffs);
@@ -126,7 +128,24 @@
body.appendf("half F = %s[6];", coeffs);
body.append("half s = sign(x);");
body.append("x = abs(x);");
- body.appendf("return s * ((x < D) ? (C * x) + F : pow(A * x + B, G) + E);");
+ switch (kind) {
+ case TFKind::sRGBish_TF:
+ body.append("x = (x < D) ? (C * x) + F : pow(A * x + B, G) + E;");
+ break;
+ case TFKind::PQish_TF:
+ body.append("x = pow(max(A + B * pow(x, C), 0) / (D + E * pow(x, C)), F);");
+ break;
+ case TFKind::HLGish_TF:
+ body.append("x = (x*A <= 1) ? pow(x*A, B) : exp((x-E)*C) + D;");
+ break;
+ case TFKind::HLGinvish_TF:
+ body.append("x = (x <= 1) ? A * pow(x, B) : C * log(x - D) + E;");
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+ body.append("return s * x;");
SkString funcName;
this->emitFunction(kHalf_GrSLType, name, SK_ARRAY_COUNT(gTFArgs), gTFArgs, body.c_str(),
&funcName);
@@ -135,12 +154,14 @@
SkString srcTFFuncName;
if (colorXformHelper->applySrcTF()) {
- srcTFFuncName = emitTFFunc("src_tf", colorXformHelper->srcTFUniform());
+ srcTFFuncName = emitTFFunc("src_tf", colorXformHelper->srcTFUniform(),
+ colorXformHelper->srcTFKind());
}
SkString dstTFFuncName;
if (colorXformHelper->applyDstTF()) {
- dstTFFuncName = emitTFFunc("dst_tf", colorXformHelper->dstTFUniform());
+ dstTFFuncName = emitTFFunc("dst_tf", colorXformHelper->dstTFUniform(),
+ colorXformHelper->dstTFKind());
}
SkString gamutXformFuncName;
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index e01b029..7d68fa4 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -957,6 +957,12 @@
- 1.498030302f * m
- 1.725879990f / (0.3520887068f + m);
}
+
+SI F approx_log(F x) {
+ const float ln2 = 0.69314718f;
+ return ln2 * approx_log2(x);
+}
+
SI F approx_pow2(F x) {
F f = fract(x);
return bit_cast<F>(round(1.0f * (1<<23),
@@ -965,6 +971,11 @@
+ 27.728023300f / (4.84252568f - f)));
}
+SI F approx_exp(F x) {
+ const float log2_e = 1.4426950408889634074f;
+ return approx_pow2(log2_e * x);
+}
+
SI F approx_powf(F x, F y) {
#if defined(SK_LEGACY_APPROX_POWF_SPECIALCASE)
return if_then_else((x == 0) , 0
@@ -1846,6 +1857,58 @@
b = fn(b);
}
+STAGE(PQish, const skcms_TransferFunction* ctx) {
+ auto fn = [&](F v) {
+ U32 sign;
+ v = strip_sign(v, &sign);
+
+ F r = approx_powf(max(mad(ctx->b, approx_powf(v, ctx->c), ctx->a), 0)
+ / (mad(ctx->e, approx_powf(v, ctx->c), ctx->d)),
+ ctx->f);
+
+ return apply_sign(r, sign);
+ };
+ r = fn(r);
+ g = fn(g);
+ b = fn(b);
+}
+
+STAGE(HLGish, const skcms_TransferFunction* ctx) {
+ auto fn = [&](F v) {
+ U32 sign;
+ v = strip_sign(v, &sign);
+
+ const float R = ctx->a, G = ctx->b,
+ a = ctx->c, b = ctx->d, c = ctx->e;
+
+ F r = if_then_else(v*R <= 1, approx_powf(v*R, G)
+ , approx_exp((v-c)*a) + b);
+
+ return apply_sign(r, sign);
+ };
+ r = fn(r);
+ g = fn(g);
+ b = fn(b);
+}
+
+STAGE(HLGinvish, const skcms_TransferFunction* ctx) {
+ auto fn = [&](F v) {
+ U32 sign;
+ v = strip_sign(v, &sign);
+
+ const float R = ctx->a, G = ctx->b,
+ a = ctx->c, b = ctx->d, c = ctx->e;
+
+ F r = if_then_else(v <= 1, R * approx_powf(v, G)
+ , a * approx_log(v - b) + c);
+
+ return apply_sign(r, sign);
+ };
+ r = fn(r);
+ g = fn(g);
+ b = fn(b);
+}
+
STAGE(from_srgb, Ctx::None) {
auto fn = [](F s) {
U32 sign;
@@ -4236,6 +4299,9 @@
NOT_IMPLEMENTED(matrix_4x3) // TODO
NOT_IMPLEMENTED(parametric)
NOT_IMPLEMENTED(gamma_)
+ NOT_IMPLEMENTED(PQish)
+ NOT_IMPLEMENTED(HLGish)
+ NOT_IMPLEMENTED(HLGinvish)
NOT_IMPLEMENTED(rgb_to_hsl)
NOT_IMPLEMENTED(hsl_to_rgb)
NOT_IMPLEMENTED(gauss_a_to_rgba) // TODO