color: implement perceptual quantizer transfer function

This implements WESTON_TF_ST2084_PQ.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2025-03-25 21:23:45 -03:00 committed by Pekka Paalanen
parent 0af48f0415
commit 8762c3bab5
5 changed files with 176 additions and 16 deletions

View file

@ -26,6 +26,7 @@
#include "config.h"
#include "color.h"
#include "color-properties.h"
#include "color-operations.h"
#include "shared/helpers.h"
@ -122,6 +123,63 @@ sample_powlin(float params[3][MAX_PARAMS_PARAM_CURVE], uint32_t ch,
}
}
static float
perceptual_quantizer(float x)
{
float aux, c1, c2, c3, m1_inv, m2_inv;
m1_inv = 1.0 / 0.1593017578125;
m2_inv = 1.0 / 78.84375;
c1 = 0.8359375;
c2 = 18.8515625;
c3 = 18.6875;
aux = pow(x, m2_inv);
/* Normalized result. We don't take into consideration the luminance
* levels, as we don't receive the input as nits, but normalized in the
* [0, 1] range. */
return pow(MAX(aux - c1, 0.0) / (c2 - c3 * aux), m1_inv);
}
static float
perceptual_quantizer_inverse(float x)
{
float aux, c1, c2, c3, m1, m2;
m1 = 0.1593017578125;
m2 = 78.84375;
c1 = 0.8359375;
c2 = 18.8515625;
c3 = 18.6875;
aux = pow(x, m1);
/* Normalized result. We don't take into consideration the luminance
* levels, as we don't receive the input as nits, but normalized in the
* [0, 1] range. */
return pow((c1 + c2 * aux) / (1.0 + c3 * aux), m2);
}
static void
sample_pq(enum weston_tf_direction tf_direction, uint32_t ch, uint32_t len,
float *in, float *out)
{
unsigned int i;
float x;
for (i = 0; i < len; i++) {
/**
* PQ and inverse PQ are always clamped, undefined for values
* out of [0, 1] range.
*/
x = ensure_unorm(in[i]);
if (tf_direction == WESTON_FORWARD_TF)
out[i] = perceptual_quantizer(x);
else
out[i] = perceptual_quantizer_inverse(x);
}
}
/**
* Given a color curve and a channel, sample an input.
*
@ -146,13 +204,23 @@ weston_color_curve_sample(struct weston_compositor *compositor,
switch(curve->type) {
case WESTON_COLOR_CURVE_TYPE_ENUM:
/* Lower the enum curve to a param curve and we'll handle that below. */
ret = weston_color_curve_enum_get_parametric(compositor,
&curve->u.enumerated,
&parametric);
if (!ret)
return false;
goto param;
/**
* If the TF of the enum curve is implemented, sample from that.
* Otherwise, fallback to a parametric curve and we'll handle
* that below.
*/
switch(curve->u.enumerated.tf->tf) {
case WESTON_TF_ST2084_PQ:
sample_pq(curve->u.enumerated.tf_direction, ch, len, in, out);
return true;
default:
ret = weston_color_curve_enum_get_parametric(compositor,
&curve->u.enumerated,
&parametric);
if (!ret)
return false;
goto param;
}
case WESTON_COLOR_CURVE_TYPE_PARAMETRIC:
/* Parametric curve, let's copy it and we'll handle that below. */
parametric = curve->u.parametric;

View file

@ -46,6 +46,8 @@
#define SHADER_COLOR_CURVE_LUT_3x1D 1
#define SHADER_COLOR_CURVE_LINPOW 2
#define SHADER_COLOR_CURVE_POWLIN 3
#define SHADER_COLOR_CURVE_PQ 4
#define SHADER_COLOR_CURVE_PQ_INVERSE 5
/* enum gl_shader_color_mapping */
#define SHADER_COLOR_MAPPING_IDENTITY 0
@ -348,6 +350,62 @@ sample_powlin_vec3(float params[MAX_CURVESET_PARAMS], bool must_clamp,
sample_powlin(params, must_clamp, color.b, 2));
}
float
perceptual_quantizer(float x)
{
float aux, c1, c2, c3, m1_inv, m2_inv;
m1_inv = 1.0 / 0.1593017578125;
m2_inv = 1.0 / 78.84375;
c1 = 0.8359375;
c2 = 18.8515625;
c3 = 18.6875;
aux = pow(x, m2_inv);
/* Normalized result. We don't take into consideration the luminance
* levels, as that's only useful when converting the optical values to
* nits, but we return them in the [0, 1] range. */
return pow(max(aux - c1, 0.0) / (c2 - c3 * aux), m1_inv);
}
float
perceptual_quantizer_inverse(float x)
{
float aux, c1, c2, c3, m1, m2;
m1 = 0.1593017578125;
m2 = 78.84375;
c1 = 0.8359375;
c2 = 18.8515625;
c3 = 18.6875;
aux = pow(x, m1);
/* Normalized result. We don't take into consideration the luminance
* levels, as we don't receive the input as nits, but normalized in the
* [0, 1] range. */
return pow((c1 + c2 * aux) / (1.0 + c3 * aux), m2);
}
float
sample_perceptual_quantizer(compile_const bool inverse, float x)
{
/* PQ and its inverse are only defined for input values in this range. */
x = clamp(x, 0.0, 1.0);
if (inverse)
return perceptual_quantizer_inverse(x);
return perceptual_quantizer(x);
}
vec3
sample_perceptual_quantizer_vec3(compile_const bool inverse, vec3 color)
{
return vec3(sample_perceptual_quantizer(inverse, color.r),
sample_perceptual_quantizer(inverse, color.g),
sample_perceptual_quantizer(inverse, color.b));
}
vec3
color_pre_curve(vec3 color)
{
@ -365,6 +423,12 @@ color_pre_curve(vec3 color)
return sample_powlin_vec3(color_pre_curve_params,
color_pre_curve_clamped_input,
color);
} else if (c_color_pre_curve == SHADER_COLOR_CURVE_PQ) {
return sample_perceptual_quantizer_vec3(false, /* not inverse */
color);
} else if (c_color_pre_curve == SHADER_COLOR_CURVE_PQ_INVERSE) {
return sample_perceptual_quantizer_vec3(true, /* inverse */
color);
} else {
/* Never reached, bad c_color_pre_curve. */
return vec3(1.0, 0.3, 1.0);
@ -412,6 +476,12 @@ color_post_curve(vec3 color)
return sample_powlin_vec3(color_post_curve_params,
color_post_curve_clamped_input,
color);
} else if (c_color_post_curve == SHADER_COLOR_CURVE_PQ) {
return sample_perceptual_quantizer_vec3(false, /* not inverse */
color);
} else if (c_color_post_curve == SHADER_COLOR_CURVE_PQ_INVERSE) {
return sample_perceptual_quantizer_vec3(true, /* inverse */
color);
} else {
/* Never reached, bad c_color_post_curve. */
return vec3(1.0, 0.3, 1.0);

View file

@ -223,6 +223,8 @@ enum gl_shader_color_curve {
SHADER_COLOR_CURVE_LUT_3x1D,
SHADER_COLOR_CURVE_LINPOW,
SHADER_COLOR_CURVE_POWLIN,
SHADER_COLOR_CURVE_PQ,
SHADER_COLOR_CURVE_PQ_INVERSE,
};
/* Keep the following in sync with fragment.glsl. */
@ -282,15 +284,15 @@ struct gl_shader_requirements
bool tint:1;
bool wireframe:1;
unsigned color_pre_curve:2; /* enum gl_shader_color_curve */
unsigned color_pre_curve:3; /* enum gl_shader_color_curve */
unsigned color_mapping:2; /* enum gl_shader_color_mapping */
unsigned color_post_curve:2; /* enum gl_shader_color_curve */
unsigned color_post_curve:3; /* enum gl_shader_color_curve */
/*
* The total size of all bitfields plus pad_bits_ must fill up exactly
* how many bytes the compiler allocates for them together.
*/
unsigned pad_bits_:18;
unsigned pad_bits_:16;
};
static_assert(sizeof(struct gl_shader_requirements) ==
4 /* total bitfield size in bytes */,

View file

@ -31,6 +31,7 @@
#include <libweston/libweston.h>
#include "color.h"
#include "color-properties.h"
#include "gl-renderer.h"
#include "gl-renderer-internal.h"
@ -77,6 +78,8 @@ gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve)
{
switch (gl_curve->type) {
case SHADER_COLOR_CURVE_IDENTITY:
case SHADER_COLOR_CURVE_PQ:
case SHADER_COLOR_CURVE_PQ_INVERSE:
case SHADER_COLOR_CURVE_LINPOW:
case SHADER_COLOR_CURVE_POWLIN:
break;
@ -175,12 +178,23 @@ gl_color_curve_enum(struct gl_renderer *gr,
struct weston_color_curve_parametric parametric;
bool ret;
/* Lower TF to a parametric curve. */
ret = weston_color_curve_enum_get_parametric(gr->compositor,
&curve->u.enumerated,
&parametric);
if (!ret)
return false;
/**
* Handle enum curve (if TF is implemented) or fallback to a parametric
* curve.
*/
switch(curve->u.enumerated.tf->tf) {
case WESTON_TF_ST2084_PQ:
gl_curve->type = (curve->u.enumerated.tf_direction == WESTON_FORWARD_TF) ?
SHADER_COLOR_CURVE_PQ : SHADER_COLOR_CURVE_PQ_INVERSE;
return true;
default:
ret = weston_color_curve_enum_get_parametric(gr->compositor,
&curve->u.enumerated,
&parametric);
if (!ret)
return false;
break;
}
/* Handle parametric curve that we got from TF. */
@ -387,6 +401,8 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr,
sconf->req.color_pre_curve = gl_xform->pre_curve.type;
switch (gl_xform->pre_curve.type) {
case SHADER_COLOR_CURVE_IDENTITY:
case SHADER_COLOR_CURVE_PQ:
case SHADER_COLOR_CURVE_PQ_INVERSE:
break;
case SHADER_COLOR_CURVE_LUT_3x1D:
sconf->color_pre_curve.lut_3x1d.tex = gl_xform->pre_curve.u.lut_3x1d.tex;
@ -406,6 +422,8 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr,
sconf->req.color_post_curve = gl_xform->post_curve.type;
switch (gl_xform->post_curve.type) {
case SHADER_COLOR_CURVE_IDENTITY:
case SHADER_COLOR_CURVE_PQ:
case SHADER_COLOR_CURVE_PQ_INVERSE:
break;
case SHADER_COLOR_CURVE_LUT_3x1D:
sconf->color_post_curve.lut_3x1d.tex = gl_xform->post_curve.u.lut_3x1d.tex;

View file

@ -136,6 +136,8 @@ gl_shader_color_curve_to_string(enum gl_shader_color_curve kind)
CASERET(SHADER_COLOR_CURVE_LUT_3x1D)
CASERET(SHADER_COLOR_CURVE_LINPOW)
CASERET(SHADER_COLOR_CURVE_POWLIN)
CASERET(SHADER_COLOR_CURVE_PQ)
CASERET(SHADER_COLOR_CURVE_PQ_INVERSE)
#undef CASERET
}