Implement GammaExpansionEffect using ALU ops instead of texture lookups.
[movit] / gamma_expansion_effect.cpp
index 4574567..efec553 100644 (file)
@@ -1,6 +1,7 @@
 #include <math.h>
 #include <assert.h>
 
+#include "effect_util.h"
 #include "gamma_expansion_effect.h"
 #include "util.h"
 
@@ -8,8 +9,6 @@ GammaExpansionEffect::GammaExpansionEffect()
        : source_curve(GAMMA_LINEAR)
 {
        register_int("source_curve", (int *)&source_curve);
-       memset(expansion_curve, 0, sizeof(expansion_curve));
-       register_1d_texture("expansion_curve_tex", expansion_curve, EXPANSION_CURVE_SIZE);
 }
 
 std::string GammaExpansionEffect::output_fragment_shader()
@@ -17,29 +16,104 @@ std::string GammaExpansionEffect::output_fragment_shader()
        if (source_curve == GAMMA_LINEAR) {
                return read_file("identity.frag");
        }
-       if (source_curve == GAMMA_sRGB) {
-               for (unsigned i = 0; i < EXPANSION_CURVE_SIZE; ++i) {
-                       float x = i / (float)(EXPANSION_CURVE_SIZE - 1);
-                       if (x < 0.04045f) {
-                               expansion_curve[i] = (1.0/12.92f) * x;
-                       } else {
-                               expansion_curve[i] = pow((x + 0.055) * (1.0/1.055f), 2.4);
-                       }
-               }
-               invalidate_1d_texture("expansion_curve_tex");
-               return read_file("gamma_expansion_effect.frag");
-       }
-       if (source_curve == GAMMA_REC_709) {  // And Rec. 601.
-               for (unsigned i = 0; i < EXPANSION_CURVE_SIZE; ++i) {
-                       float x = i / (float)(EXPANSION_CURVE_SIZE - 1);
-                       if (x < 0.081f) {
-                               expansion_curve[i] = (1.0/4.5f) * x;
-                       } else {
-                               expansion_curve[i] = pow((x + 0.099) * (1.0/1.099f), 1.0f/0.45f);
-                       }
-               }
-               invalidate_1d_texture("expansion_curve_tex");
+       if (source_curve == GAMMA_sRGB ||
+           source_curve == GAMMA_REC_709 ||  // Also includes Rec. 601, and 10-bit Rec. 2020.
+           source_curve == GAMMA_REC_2020_12_BIT) {
                return read_file("gamma_expansion_effect.frag");
        }
        assert(false);
 }
+
+void GammaExpansionEffect::set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num)
+{
+       Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
+
+       // All of these curves follow a continuous curve that's piecewise defined;
+       // very low values (up to some β) are linear. Above β, we have a power curve
+       // that looks like this:
+       //
+       //   y = ((x + ɑ - 1) / ɑ)^β
+       //
+       // However, pow() is relatively slow in GLSL, so we approximate this
+       // part by a minimax polynomial, whose coefficients are precalculated
+       // in Maple. (It is very hard to accurately model the curve as a whole
+       // using minimax polynomials; both Maple and Mathematically generally
+       // just error out if you ask them to optimize over 0..1 with a higher-degree
+       // polynomial.)
+       //
+       // We put some extra weight on areas near β to keep a continuous curve,
+       // and near 1.0, since we'd really like f(1.0) = 1.0, or approximately so.
+       // The following Maple commands, using sRGB below as an example, will
+       // compute the coefficients:
+       //
+       // > alpha := 1.055;
+       // > beta := 0.04045;
+       // > gamma_ := 2.4;
+       // > w := x -> piecewise(x < beta + 0.001, 10, x > 0.999, 10, 1);
+       // > numapprox[minimax](((x + alpha - 1) / alpha)^gamma_, x=beta..1, [4,0], w(x), 'maxerror');
+       //
+       // The variable 'maxerror' will then contain the maximum absolute error
+       // at any point of the curve, and we report this along with the absolute
+       // error at beta and at 1.0. Keep in mind that along this curve,
+       // the smallest minimum difference between any two 8-bit sRGB pixel levels
+       // (in the exponential part of the curve) in linear light is that
+       // between 11/255 and 12/255, which is about 0.00033 (or three to four
+       // times of the sRGB maxerror below). The choice of a fourth-degree
+       // polynomial was made with this in mind; we have not cared equally
+       // much about 10- and 12-bit Rec. 2020.
+       //
+       // NOTE: The error at beta is compared to the _linear_ part of the curve.
+       // Since the standards give these with only a few decimals, it means that
+       // the linear and exponential parts will not match up exactly, and even
+       // a perfect approximation will have error > 0 here; sometimes, even larger
+       // than maxerror for the curve itself.
+
+       if (source_curve == GAMMA_sRGB) {
+               // From the Wikipedia article on sRGB; ɑ (called a+1 there) = 1.055,
+               // β = 0.04045, ɣ = 2.4.
+               // maxerror      = 0.000094
+               // error at beta = 0.000094
+               // error at 1.0  = 0.000094
+               set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 12.92);
+               set_uniform_float(glsl_program_num, prefix, "c0", 0.001324469581);
+               set_uniform_float(glsl_program_num, prefix, "c1", 0.02227416690);
+               set_uniform_float(glsl_program_num, prefix, "c2", 0.5917615253);
+               set_uniform_float(glsl_program_num, prefix, "c3", 0.4733532353);
+               set_uniform_float(glsl_program_num, prefix, "c4", -0.08880738120);
+               set_uniform_float(glsl_program_num, prefix, "beta", 0.04045);
+       }
+       if (source_curve == GAMMA_REC_709) {  // Also includes Rec. 601, and 10-bit Rec. 2020.
+               // Rec. 2020, page 3; ɑ = 1.099, β = 0.018 * 4.5, ɣ = 1/0.45.
+               // maxerror      = 0.000043
+               // error at beta = 0.000051 (see note above!)
+               // error at 1.0  = 0.000004
+               //
+               // Note that Rec. 2020 only gives the other direction, which is why
+               // our beta and gamma are different from the numbers mentioned
+               // (we've inverted the formula).
+               set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 4.5);
+               set_uniform_float(glsl_program_num, prefix, "c0", 0.005137028744);
+               set_uniform_float(glsl_program_num, prefix, "c1", 0.09802596889);
+               set_uniform_float(glsl_program_num, prefix, "c2", 0.7255768864);
+               set_uniform_float(glsl_program_num, prefix, "c3", 0.2135067966);
+               set_uniform_float(glsl_program_num, prefix, "c4", -0.04225094667);
+               set_uniform_float(glsl_program_num, prefix, "beta", 0.018 * 4.5);
+       }
+       if (source_curve == GAMMA_REC_2020_12_BIT) {
+               // Rec. 2020, page 3; ɑ = 1.0993, β = 0.0181 * 4.5, ɣ = 1/0.45.
+               // maxerror      = 0.000042
+               // error at beta = 0.000005
+               // error at 1.0  = 0.000004
+               //
+               // Note that Rec. 2020 only gives the other direction, which is why
+               // our beta and gamma are different from the numbers mentioned
+               // (we've inverted the formula).
+               set_uniform_float(glsl_program_num, prefix, "linear_scale", 1.0 / 4.5);
+               set_uniform_float(glsl_program_num, prefix, "c0", 0.005167545928);
+               set_uniform_float(glsl_program_num, prefix, "c1", 0.09835585809);
+               set_uniform_float(glsl_program_num, prefix, "c2", 0.7254820139);
+               set_uniform_float(glsl_program_num, prefix, "c3", 0.2131291155);
+               set_uniform_float(glsl_program_num, prefix, "c4", -0.04213877222);
+               set_uniform_float(glsl_program_num, prefix, "beta", 0.0181 * 4.5);
+       }
+}