547 lines
17 KiB
C
547 lines
17 KiB
C
/////////////////////////////
|
|
// SRGB Encoding Helpers
|
|
/////////////////////////////
|
|
|
|
// Go from sRGB encoding -> linear
|
|
vec3 srgbToLinear(vec3 color) {
|
|
bvec3 isLo = lessThanEqual(color, vec3(0.04045f));
|
|
|
|
vec3 loPart = color / 12.92f;
|
|
vec3 hiPart = pow((color + 0.055f) / 1.055f, vec3(12.0f / 5.0f));
|
|
return mix(hiPart, loPart, isLo);
|
|
}
|
|
|
|
vec4 srgbToLinear(vec4 color) {
|
|
return vec4(srgbToLinear(color.rgb), color.a);
|
|
}
|
|
|
|
// Go from linear -> sRGB encoding.
|
|
vec3 linearToSrgb(vec3 color) {
|
|
bvec3 isLo = lessThanEqual(color, vec3(0.0031308f));
|
|
|
|
vec3 loPart = color * 12.92f;
|
|
vec3 hiPart = pow(color, vec3(5.0f / 12.0f)) * 1.055f - 0.055f;
|
|
return mix(hiPart, loPart, isLo);
|
|
}
|
|
|
|
vec4 linearToSrgb(vec4 color) {
|
|
return vec4(linearToSrgb(color.rgb), color.a);
|
|
}
|
|
|
|
/////////////////////////////
|
|
// PQ Encoding Helpers
|
|
/////////////////////////////
|
|
|
|
// Converts nits -> pq and pq -> nits
|
|
// Does NOT affect primaries at all.
|
|
vec3 nitsToPq(vec3 nits) {
|
|
vec3 y = clamp(nits / 10000.0, vec3(0.0), vec3(1.0));
|
|
const float c1 = 0.8359375;
|
|
const float c2 = 18.8515625;
|
|
const float c3 = 18.6875;
|
|
const float m1 = 0.1593017578125;
|
|
const float m2 = 78.84375;
|
|
vec3 num = c1 + c2 * pow(y, vec3(m1));
|
|
vec3 den = 1.0 + c3 * pow(y, vec3(m1));
|
|
vec3 n = pow(num / den, vec3(m2));
|
|
return n;
|
|
}
|
|
|
|
vec3 pqToNits(vec3 pq) {
|
|
const float c1 = 0.8359375;
|
|
const float c2 = 18.8515625;
|
|
const float c3 = 18.6875;
|
|
|
|
const float oo_m1 = 1.0 / 0.1593017578125;
|
|
const float oo_m2 = 1.0 / 78.84375;
|
|
|
|
vec3 num = max(pow(pq, vec3(oo_m2)) - c1, vec3(0.0));
|
|
vec3 den = c2 - c3 * pow(pq, vec3(oo_m2));
|
|
|
|
return 10000.0 * pow(num / den, vec3(oo_m1));
|
|
}
|
|
|
|
// does NOT change primaries, just
|
|
// the pq value in nits / 80.0f!
|
|
vec3 pqToScRGBEncoding(vec3 pq)
|
|
{
|
|
return pqToNits(pq) / 80.0f;
|
|
}
|
|
|
|
vec3 scRGBEncodingToPQ(vec3 scRGBEncodedValue)
|
|
{
|
|
return nitsToPq(scRGBEncodedValue * 80.0f);
|
|
}
|
|
|
|
// This is apparently defined at 80 nits...
|
|
// May want to take liberties with this when displaying
|
|
// on SDR though... 100 may be a better fit for most content
|
|
// to match typical sRGB mastering.
|
|
const float c_scRGBLightScale = 80.0f;
|
|
vec3 scrgbToNits(vec3 scRGB) {
|
|
return scRGB * c_scRGBLightScale;
|
|
}
|
|
|
|
vec3 nitsToScRGB(vec3 nits) {
|
|
return nits / c_scRGBLightScale;
|
|
}
|
|
|
|
// nits -> linear (nits / scale)
|
|
vec3 nitsToLinear(vec3 nits) {
|
|
return nits * u_nitsToLinear;
|
|
}
|
|
|
|
// linear -> nits (linear * scale)
|
|
vec3 linearToNits(vec3 linear) {
|
|
return linear * u_linearToNits;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Primary Conversion Helpers
|
|
/////////////////////////////
|
|
|
|
struct PrimaryInfo {
|
|
vec2 displayPrimaryRed;
|
|
vec2 displayPrimaryGreen;
|
|
vec2 displayPrimaryBlue;
|
|
vec2 whitePoint;
|
|
};
|
|
|
|
vec3 convert_primary(vec2 xy) {
|
|
float X = xy.x / xy.y;
|
|
float Y = 1.0f;
|
|
float Z = (1.0f - xy.x - xy.y) / xy.y;
|
|
return vec3(X, Y, Z);
|
|
}
|
|
|
|
mat3 compute_xyz_matrix(PrimaryInfo metadata) {
|
|
vec3 red = convert_primary(metadata.displayPrimaryRed);
|
|
vec3 green = convert_primary(metadata.displayPrimaryGreen);
|
|
vec3 blue = convert_primary(metadata.displayPrimaryBlue);
|
|
vec3 white = convert_primary(metadata.whitePoint);
|
|
|
|
vec3 component_scale = inverse(mat3(red, green, blue)) * white;
|
|
return transpose(mat3(red * component_scale.x, green * component_scale.y, blue * component_scale.z));
|
|
}
|
|
|
|
const PrimaryInfo rec709_primaries = {
|
|
vec2(0.640f, 0.330f), // red
|
|
vec2(0.300f, 0.600f), // green
|
|
vec2(0.150f, 0.060f), // blue
|
|
vec2(0.3127f, 0.3290f), // whitepoint
|
|
};
|
|
/*const*/ mat3 rec709_to_xyz = compute_xyz_matrix(rec709_primaries);
|
|
/*const*/ mat3 xyz_to_rec709 = inverse(rec709_to_xyz);
|
|
|
|
const PrimaryInfo rec2020_primaries = {
|
|
vec2(0.708f, 0.292f), // red
|
|
vec2(0.170f, 0.797f), // green
|
|
vec2(0.131f, 0.046f), // blue
|
|
vec2(0.3127f, 0.3290f), // whitepoint
|
|
};
|
|
/*const*/ mat3 rec2020_to_xyz = compute_xyz_matrix(rec2020_primaries);
|
|
/*const*/ mat3 xyz_to_rec2020 = inverse(rec2020_to_xyz);
|
|
|
|
vec3 convert_primaries(vec3 color, mat3 src_to_xyz, mat3 xyz_to_dst) {
|
|
return color * mat3(src_to_xyz * xyz_to_dst);
|
|
}
|
|
|
|
// Rep. ITU-R BT.2446-1 Table 2-4 (inversed)
|
|
// BT.2446 Method A inverse tone mapping (itm)
|
|
vec3 bt2446a_inverse_tonemapping(
|
|
vec3 color,
|
|
float sdr_nits,
|
|
float target_nits)
|
|
{
|
|
const vec3 k_bt2020 = vec3(0.262698338956556, 0.678008765772817, 0.0592928952706273);
|
|
const float k_bt2020_r_helper = 1.47460332208689; // 2 - 2 * 0.262698338956556
|
|
const float k_bt2020_b_helper = 1.88141420945875; // 2 - 2 * 0.0592928952706273
|
|
|
|
//gamma
|
|
const float inverse_gamma = 2.4f;
|
|
const float gamma = 1.f / inverse_gamma;
|
|
|
|
//RGB->R'G'B' gamma compression
|
|
color = pow(color, vec3(gamma));
|
|
|
|
// Rec. ITU-R BT.2020-2 Table 4
|
|
//Y'tmo
|
|
const float y_tmo = dot(color, k_bt2020);
|
|
//C'b,tmo
|
|
const float c_b_tmo = (color.b - y_tmo) /
|
|
k_bt2020_b_helper;
|
|
//C'r,tmo
|
|
const float c_r_tmo = (color.r - y_tmo) /
|
|
k_bt2020_r_helper;
|
|
|
|
// fast path as per Rep. ITU-R BT.2446-1 Table 4
|
|
// matches the output of the inversed version for the given input
|
|
if ((sdr_nits > 99.f && sdr_nits < 101.f) && (target_nits > 999.f && target_nits < 1001.f))
|
|
//avoid float issues
|
|
{
|
|
sdr_nits = 100.f;
|
|
target_nits = 1000.f;
|
|
|
|
const float a1 = 1.8712e-5;
|
|
const float b1 = -2.7334e-3;
|
|
const float c1 = 1.3141;
|
|
const float a2 = 2.8305e-6;
|
|
const float b2 = -7.4622e-4;
|
|
const float c2 = 1.2328;
|
|
|
|
const float yy_ = 255.0f * y_tmo;
|
|
|
|
const float t = 70;
|
|
|
|
float e = yy_ <= t ?
|
|
a1 * pow(yy_, 2.f) + b1 * yy_ + c1 :
|
|
a2 * pow(yy_, 2.f) + b2 * yy_ + c2;
|
|
|
|
const float y_hdr = pow(yy_, e);
|
|
|
|
float s_c = y_tmo > 0.f ?
|
|
1.075f * (y_hdr / y_tmo) :
|
|
1.f;
|
|
|
|
const float c_b_hdr = c_b_tmo * s_c;
|
|
const float c_r_hdr = c_r_tmo * s_c;
|
|
|
|
color = vec3(clamp(y_hdr + k_bt2020_r_helper * c_r_hdr, 0.f, 1000.f),
|
|
clamp(y_hdr - 0.16455312684366 * c_b_hdr - 0.57135312684366 * c_r_hdr, 0.f, 1000.f),
|
|
clamp(y_hdr + k_bt2020_b_helper * c_b_hdr, 0.f, 1000.f));
|
|
color /= 1000.f;
|
|
}
|
|
else
|
|
{
|
|
// adjusted luma component (inverse)
|
|
// get Y'sdr
|
|
const float y_sdr = y_tmo + max(0.1f * c_r_tmo, 0.f);
|
|
|
|
// Tone mapping step 3 (inverse)
|
|
// get Y'c
|
|
const float p_sdr = 1 + 32 * pow(
|
|
sdr_nits /
|
|
10000.f
|
|
, gamma);
|
|
//Y'c
|
|
const float y_c = log((y_sdr * (p_sdr - 1)) + 1) /
|
|
log(p_sdr); //log = ln
|
|
|
|
// Tone mapping step 2 (inverse)
|
|
// get Y'p
|
|
float y_p = 0.f;
|
|
|
|
const float y_p_0 = y_c / 1.0770f;
|
|
const float y_p_2 = (y_c - 0.5000f) /
|
|
0.5000f;
|
|
|
|
const float _first = -2.7811f;
|
|
const float _sqrt = sqrt(4.83307641 - 4.604 * y_c);
|
|
const float _div = -2.302f;
|
|
const float y_p_1 = (_first + _sqrt) /
|
|
_div;
|
|
|
|
if (y_p_0 <= 0.7399f)
|
|
y_p = y_p_0;
|
|
else if (y_p_1 > 0.7399f && y_p_1 < 0.9909f)
|
|
y_p = y_p_1;
|
|
else if (y_p_2 >= 0.9909f)
|
|
y_p = y_p_2;
|
|
else //y_p_1 sometimes (about 0.12% out of the full RGB range)
|
|
//is less than 0.7399f or more than 0.9909f because of float inaccuracies
|
|
{
|
|
//error is small enough (less than 0.001) for this to be OK
|
|
//ideally you would choose between y_p_0 and y_p_1 if y_p_1 < 0.7399f depending on which is closer to 0.7399f
|
|
//or between y_p_1 and y_p_2 if y_p_1 > 0.9909f depending on which is closer to 0.9909f
|
|
y_p = y_p_1;
|
|
|
|
//this clamps it to 2 float steps above 0.7399f or 2 float steps below 0.9909f
|
|
//if (y_p_1 < 0.7399f)
|
|
// y_p = 0.7399001f;
|
|
//else
|
|
// y_p = 0.99089986f;
|
|
}
|
|
|
|
// Tone mapping step 1 (inverse)
|
|
// get Y'
|
|
const float p_hdr = 1 + 32 * pow(
|
|
target_nits /
|
|
10000.f
|
|
, gamma);
|
|
//Y'
|
|
const float y_ = (pow(p_hdr, y_p) - 1) /
|
|
(p_hdr - 1);
|
|
|
|
// Colour scaling function
|
|
float col_scale = 0.f;
|
|
if (y_ > 0.f) // avoid divison by zero
|
|
col_scale = y_sdr /
|
|
(1.1f * y_);
|
|
|
|
// Colour difference signals (inverse) and Luma (inverse)
|
|
// get R'G'B'
|
|
color.b = ((c_b_tmo * k_bt2020_b_helper) /
|
|
col_scale) + y_;
|
|
color.r = ((c_r_tmo * k_bt2020_r_helper) /
|
|
col_scale) + y_;
|
|
color.g = (y_ - (k_bt2020.r * color.r + k_bt2020.b * color.b)) /
|
|
k_bt2020.g;
|
|
|
|
//safety
|
|
color.r = clamp(color.r, 0.f, 1.f);
|
|
color.g = clamp(color.g, 0.f, 1.f);
|
|
color.b = clamp(color.b, 0.f, 1.f);
|
|
}
|
|
|
|
// R'G'B' gamma expansion
|
|
color = pow(color, vec3(inverse_gamma));
|
|
|
|
// map target luminance into 10000 nits
|
|
color = color * target_nits;
|
|
|
|
return color;
|
|
}
|
|
|
|
#include "heatmap.h"
|
|
|
|
// Generic helper
|
|
|
|
vec3 colorspace_plane_degamma_tf(vec3 color, uint colorspace) {
|
|
// matches with colorspace_to_plane_degamma_tf in drm.cpp
|
|
|
|
switch (colorspace) {
|
|
default: return vec3(1, 1, 0); // should never happen
|
|
|
|
case colorspace_passthru:
|
|
case colorspace_linear: // Using sRGB image view. Unlike DRM which doesn't get that liberty for scanout.
|
|
case colorspace_scRGB:
|
|
return color;
|
|
case colorspace_sRGB:
|
|
return srgbToLinear(color);
|
|
case colorspace_pq:
|
|
return pqToScRGBEncoding(color);
|
|
}
|
|
}
|
|
|
|
vec3 colorspace_plane_regamma_tf(vec3 color, uint colorspace) {
|
|
switch (colorspace) {
|
|
default: return vec3(1, 1, 0); // should never happen
|
|
|
|
case colorspace_passthru:
|
|
case colorspace_scRGB:
|
|
return color;
|
|
case colorspace_linear: // Using sRGB image view. Unlike DRM which doesn't get that liberty for scanout.
|
|
case colorspace_sRGB:
|
|
return linearToSrgb(color);
|
|
case colorspace_pq:
|
|
return scRGBEncodingToPQ(color);
|
|
}
|
|
}
|
|
|
|
vec3 colorspace_plane_shaper_tf(vec3 color, uint colorspace) {
|
|
// matches with colorspace_to_plane_regamma_tf in drm.cpp
|
|
|
|
switch (colorspace) {
|
|
default: return vec3(0, 1, 1); // should never happen
|
|
|
|
case colorspace_linear:
|
|
case colorspace_sRGB:
|
|
return linearToSrgb(color);
|
|
case colorspace_scRGB:
|
|
case colorspace_pq:
|
|
return scRGBEncodingToPQ(color);
|
|
}
|
|
}
|
|
|
|
// pre-blend doing display EOTF -> display linearized
|
|
vec3 colorspace_blend_tf(vec3 color, uint eotf) {
|
|
switch (eotf) {
|
|
default:
|
|
return color;
|
|
|
|
// Note from Josh:
|
|
//
|
|
// We are kinda halfway between output space and not at this point
|
|
// the color primaries, gamut remapping has already been performed
|
|
// in display output 2.2 space, but that doesn't change the fact
|
|
// that we haven't displayed it yet!
|
|
//
|
|
// Perform the alpha blending with sRGB linearization (like the CONTENT specifies) here
|
|
// the primaries and gamut remapping transformations we performed in output 2.2 space do NOT matter.
|
|
// This is more correct than using gamma 2.2 for that here.
|
|
case EOTF_Gamma22:
|
|
return srgbToLinear(color);
|
|
case EOTF_PQ:
|
|
return pqToScRGBEncoding(color);
|
|
}
|
|
}
|
|
|
|
// post blend doing display linearized -> display EOTF
|
|
vec3 colorspace_output_tf(vec3 color, uint eotf) {
|
|
switch (eotf) {
|
|
default:
|
|
return color;
|
|
|
|
// see comment in colorspace_blend_tf
|
|
case EOTF_Gamma22:
|
|
return linearToSrgb(color);
|
|
case EOTF_PQ:
|
|
return scRGBEncodingToPQ(color);
|
|
}
|
|
}
|
|
|
|
// matches how we treat content here :)
|
|
uint colorspace_to_eotf(uint colorspace)
|
|
{
|
|
// matches with ColorSpaceToEOTFIndex in drm.cpp
|
|
switch ( colorspace )
|
|
{
|
|
default:
|
|
case colorspace_linear: // Not actually linear, just Linear vs sRGB image views in Vulkan. Still viewed as sRGB on the DRM side.
|
|
case colorspace_sRGB:
|
|
// SDR sRGB content treated as native Gamma 22 curve. No need to do sRGB -> 2.2 or whatever.
|
|
return EOTF_Gamma22;
|
|
case colorspace_scRGB:
|
|
// Okay, so this is WEIRD right? OKAY Let me explain it to you.
|
|
// The plan for scRGB content is to go from scRGB -> PQ in a SHAPER_TF
|
|
// before indexing into the shaper.
|
|
return EOTF_PQ;
|
|
case colorspace_pq:
|
|
return EOTF_PQ;
|
|
}
|
|
}
|
|
|
|
float half_texel_scale(float x, float half_texel)
|
|
{
|
|
return mix(0.0f + half_texel, 1.0f - half_texel, x);
|
|
}
|
|
|
|
vec3 half_texel_scale(vec3 x, vec3 half_texel)
|
|
{
|
|
return mix(vec3(0.0f) + half_texel, vec3(1.0f) - half_texel, x);
|
|
}
|
|
|
|
vec3 perform_1dlut(vec3 color, sampler1D shaperLUT) {
|
|
int size = textureSize(shaperLUT, 0);
|
|
float offset = 0.5f / float(size);
|
|
|
|
return vec3(
|
|
textureLod(shaperLUT, half_texel_scale(color.r, offset), 0.0f).r,
|
|
textureLod(shaperLUT, half_texel_scale(color.g, offset), 0.0f).g,
|
|
textureLod(shaperLUT, half_texel_scale(color.b, offset), 0.0f).b);
|
|
}
|
|
|
|
vec3 perform_3dlut_native(vec3 color, sampler3D lut3D) {
|
|
ivec3 size = textureSize(lut3D, 0);
|
|
vec3 offset = 0.5f / vec3(float(size.x), float(size.y), float(size.z));
|
|
|
|
return textureLod(lut3D, half_texel_scale(color.rgb, offset), 0.0f).rgb;
|
|
}
|
|
|
|
// Adapted from:
|
|
// https://github.com/AcademySoftwareFoundation/OpenColorIO/ops/lut3d/Lut3DOpGPU.cpp
|
|
// License available in their repo and in our LICENSE file.
|
|
vec3 perform_3dlut_tetrahedral(vec3 color, sampler3D lut3D) {
|
|
ivec3 size_i = textureSize(lut3D, 0);
|
|
// We only support uniform lut sizes so take .x's dim
|
|
float size = float(size_i.x);
|
|
float incr = 1.0f / size;
|
|
|
|
vec3 outColor = color.bgr;
|
|
|
|
vec3 coords = outColor.rgb * (vec3(size - 1.0f));
|
|
vec3 baseInd = floor(coords);
|
|
vec3 frac = coords - baseInd;
|
|
vec3 f1, f4;
|
|
baseInd = (baseInd.zyx + vec3(0.5)) / vec3(size);
|
|
vec3 v1 = textureLod(lut3D, baseInd, 0).rgb;
|
|
vec3 nextInd = baseInd + vec3(incr);
|
|
vec3 v4 = textureLod(lut3D, nextInd, 0).rgb;
|
|
if (frac.r >= frac.g)
|
|
{
|
|
if (frac.g >= frac.b)
|
|
{
|
|
nextInd = baseInd + vec3(0, 0, incr);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(0, incr, incr);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.r);
|
|
f4 = vec3(frac.b);
|
|
vec3 f2 = vec3(frac.r - frac.g);
|
|
vec3 f3 = vec3(frac.g - frac.b);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
else if (frac.r >= frac.b)
|
|
{
|
|
nextInd = baseInd + vec3(0, 0, incr);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(incr, 0, incr);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.r);
|
|
f4 = vec3(frac.g);
|
|
vec3 f2 = vec3(frac.r - frac.b);
|
|
vec3 f3 = vec3(frac.b - frac.g);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
else
|
|
{
|
|
nextInd = baseInd + vec3(incr, 0, 0);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(incr, 0, incr);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.b);
|
|
f4 = vec3(frac.g);
|
|
vec3 f2 = vec3(frac.b - frac.r);
|
|
vec3 f3 = vec3(frac.r - frac.g);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (frac.g <= frac.b)
|
|
{
|
|
nextInd = baseInd + vec3(incr, 0, 0);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(incr, incr, 0);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.b);
|
|
f4 = vec3(frac.r);
|
|
vec3 f2 = vec3(frac.b - frac.g);
|
|
vec3 f3 = vec3(frac.g - frac.r);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
else if (frac.r >= frac.b)
|
|
{
|
|
nextInd = baseInd + vec3(0, incr, 0);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(0, incr, incr);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.g);
|
|
f4 = vec3(frac.b);
|
|
vec3 f2 = vec3(frac.g - frac.r);
|
|
vec3 f3 = vec3(frac.r - frac.b);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
else
|
|
{
|
|
nextInd = baseInd + vec3(0, incr, 0);
|
|
vec3 v2 = textureLod(lut3D, nextInd, 0).rgb;
|
|
nextInd = baseInd + vec3(incr, incr, 0);
|
|
vec3 v3 = textureLod(lut3D, nextInd, 0).rgb;
|
|
f1 = vec3(1.0f - frac.g);
|
|
f4 = vec3(frac.r);
|
|
vec3 f2 = vec3(frac.g - frac.b);
|
|
vec3 f3 = vec3(frac.b - frac.r);
|
|
outColor.rgb = (f2 * v2) + (f3 * v3);
|
|
}
|
|
}
|
|
outColor.rgb = outColor.rgb + (f1 * v1) + (f4 * v4);
|
|
|
|
return outColor.rgb;
|
|
}
|
|
|
|
vec3 perform_3dlut(vec3 color, sampler3D lut3D)
|
|
{
|
|
return perform_3dlut_tetrahedral(color, lut3D);
|
|
}
|
|
|