From 11da542a0d6328058d7316ce7eb1d722cf49e4d3 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Sun, 22 May 2022 01:19:17 -0700 Subject: [PATCH] libobs: Add max_luminance to obs_source_frame Used in situations where source luminance is greater than HDR nominal peak setting to avoid clipping by applying BT.2408 maxRGB EETF. --- docs/sphinx/reference-sources.rst | 1 + libobs/data/color.effect | 58 ++++++++++++++++++---------- libobs/data/format_conversion.effect | 19 ++++----- libobs/obs-source.c | 13 +++++-- libobs/obs-video.c | 8 ++-- libobs/obs.h | 1 + 6 files changed, 64 insertions(+), 36 deletions(-) diff --git a/docs/sphinx/reference-sources.rst b/docs/sphinx/reference-sources.rst index b2941e5ef..41923ac42 100644 --- a/docs/sphinx/reference-sources.rst +++ b/docs/sphinx/reference-sources.rst @@ -1317,6 +1317,7 @@ Functions used by sources enum video_format format; float color_matrix[16]; bool full_range; + uint16_t max_luminance; float color_range_min[3]; float color_range_max[3]; bool flip; diff --git a/libobs/data/color.effect b/libobs/data/color.effect index 32d731006..3a68c4874 100644 --- a/libobs/data/color.effect +++ b/libobs/data/color.effect @@ -61,23 +61,16 @@ float st2084_to_linear_channel(float u) return pow(abs(max(c - 0.8359375, 0.) / (18.8515625 - 18.6875 * c)), 1. / 0.1593017578); } -float3 st2084_to_linear(float3 v) +float3 st2084_to_linear(float3 rgb) { - return float3(st2084_to_linear_channel(v.r), st2084_to_linear_channel(v.g), st2084_to_linear_channel(v.b)); + return float3(st2084_to_linear_channel(rgb.r), st2084_to_linear_channel(rgb.g), st2084_to_linear_channel(rgb.b)); } -float linear_to_hlg_channel(float u) -{ - float ln2_i = 1. / log(2.); - float m = 0.17883277 / ln2_i; - return (u <= (1. / 12.)) ? sqrt(3. * u) : ((log2((12. * u) - 0.28466892) * m) + 0.55991073); -} - -float eetf_0_1000(float Lw, float maxRGB1_pq) +float eetf_0_Lmax(float maxRGB1_pq, float Lw, float Lmax) { float Lw_pq = linear_to_st2084_channel(Lw / 10000.); - float E1 = maxRGB1_pq / Lw_pq; - float maxLum = linear_to_st2084_channel(.1) / Lw_pq; + float E1 = saturate(maxRGB1_pq / Lw_pq); // Ensure normalization in case Lw is a lie + float maxLum = linear_to_st2084_channel(Lmax / 10000.) / Lw_pq; float KS = (1.5 * maxLum) - 0.5; float E2 = E1; if (E1 > KS) @@ -93,18 +86,43 @@ float eetf_0_1000(float Lw, float maxRGB1_pq) return E4; } -float3 maxRGB_eetf(float3 rgb, float Lw, float Lmax) +float3 maxRGB_eetf_internal(float3 rgb_linear, float maxRGB1_linear, float maxRGB1_pq, float Lw, float Lmax) { - float maxRGB_linear = max(max(rgb.r, rgb.g), rgb.b); - float maxRGB1_pq = linear_to_st2084_channel(maxRGB_linear); - float maxRGB2_pq = eetf_0_1000(Lw, maxRGB1_pq); + float maxRGB2_pq = eetf_0_Lmax(maxRGB1_pq, Lw, Lmax); float maxRGB2_linear = st2084_to_linear_channel(maxRGB2_pq); // avoid divide-by-zero possibility - maxRGB_linear = max(6.10352e-5, maxRGB_linear); + maxRGB1_linear = max(6.10352e-5, maxRGB1_linear); - rgb *= maxRGB2_linear / maxRGB_linear; - return rgb; + rgb_linear *= maxRGB2_linear / maxRGB1_linear; + return rgb_linear; +} + +float3 maxRGB_eetf_pq_to_linear(float3 rgb_pq, float Lw, float Lmax) +{ + float3 rgb_linear = st2084_to_linear(rgb_pq); + float maxRGB1_linear = max(max(rgb_linear.r, rgb_linear.g), rgb_linear.b); + float maxRGB1_pq = max(max(rgb_pq.r, rgb_pq.g), rgb_pq.b); + return maxRGB_eetf_internal(rgb_linear, maxRGB1_linear, maxRGB1_pq, Lw, Lmax); +} + +float3 maxRGB_eetf_linear_to_linear(float3 rgb_linear, float Lw, float Lmax) +{ + float maxRGB1_linear = max(max(rgb_linear.r, rgb_linear.g), rgb_linear.b); + float maxRGB1_pq = linear_to_st2084_channel(maxRGB1_linear); + return maxRGB_eetf_internal(rgb_linear, maxRGB1_linear, maxRGB1_pq, Lw, Lmax); +} + +float3 st2084_to_linear_eetf(float3 rgb, float Lw, float Lmax) +{ + return (Lw > Lmax) ? maxRGB_eetf_pq_to_linear(rgb, Lw, Lmax) : st2084_to_linear(rgb); +} + +float linear_to_hlg_channel(float u) +{ + float ln2_i = 1. / log(2.); + float m = 0.17883277 / ln2_i; + return (u <= (1. / 12.)) ? sqrt(3. * u) : ((log2((12. * u) - 0.28466892) * m) + 0.55991073); } float3 linear_to_hlg(float3 rgb, float Lw) @@ -113,7 +131,7 @@ float3 linear_to_hlg(float3 rgb, float Lw) if (Lw > 1000.) { - rgb = maxRGB_eetf(rgb, Lw, 1000.); + rgb = maxRGB_eetf_linear_to_linear(rgb, Lw, 1000.); rgb *= 10000. / Lw; } else diff --git a/libobs/data/format_conversion.effect b/libobs/data/format_conversion.effect index 5d6e4484c..9fa6b8cc2 100644 --- a/libobs/data/format_conversion.effect +++ b/libobs/data/format_conversion.effect @@ -27,7 +27,8 @@ uniform float width_x2_i; uniform float maximum_over_sdr_white_nits; uniform float sdr_white_nits_over_maximum; uniform float hlg_exponent; -uniform float hlg_lw; +uniform float hdr_lw; +uniform float hdr_lmax; uniform float4 color_vec0; uniform float4 color_vec1; @@ -215,7 +216,7 @@ float PS_HLG_Y_709_2020(FragPos frag_in) : TARGET { float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb * sdr_white_nits_over_maximum; rgb = rec709_to_rec2020(rgb); - rgb = linear_to_hlg(rgb, hlg_lw); + rgb = linear_to_hlg(rgb, hdr_lw); float y = dot(color_vec0.xyz, rgb) + color_vec0.w; y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits return y; @@ -243,7 +244,7 @@ float PS_I010_HLG_Y_709_2020(FragPos frag_in) : TARGET { float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb * sdr_white_nits_over_maximum; rgb = rec709_to_rec2020(rgb); - rgb = linear_to_hlg(rgb, hlg_lw); + rgb = linear_to_hlg(rgb, hdr_lw); float y = dot(color_vec0.xyz, rgb) + color_vec0.w; return y * (1023. / 65535.); } @@ -290,7 +291,7 @@ float2 PS_HLG_UV_709_2020_WideWide(FragTexWideWide frag_in) : TARGET float3 rgb_bottomright = image.Sample(def_sampler, frag_in.uuvv.yw).rgb; float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum); rgb = rec709_to_rec2020(rgb); - rgb = linear_to_hlg(rgb, hlg_lw); + rgb = linear_to_hlg(rgb, hdr_lw); float u = dot(color_vec1.xyz, rgb) + color_vec1.w; float v = dot(color_vec2.xyz, rgb) + color_vec2.w; float2 uv = float2(u, v); @@ -366,7 +367,7 @@ float PS_I010_HLG_U_709_2020_WideWide(FragTexWideWide frag_in) : TARGET float3 rgb_bottomright = image.Sample(def_sampler, frag_in.uuvv.yw).rgb; float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum); rgb = rec709_to_rec2020(rgb); - rgb = linear_to_hlg(rgb, hlg_lw); + rgb = linear_to_hlg(rgb, hdr_lw); float u = dot(color_vec1.xyz, rgb) + color_vec1.w; return u * (1023. / 65535.); } @@ -404,7 +405,7 @@ float PS_I010_HLG_V_709_2020_WideWide(FragTexWideWide frag_in) : TARGET float3 rgb_bottomright = image.Sample(def_sampler, frag_in.uuvv.yw).rgb; float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum); rgb = rec709_to_rec2020(rgb); - rgb = linear_to_hlg(rgb, hlg_lw); + rgb = linear_to_hlg(rgb, hdr_lw); float v = dot(color_vec2.xyz, rgb) + color_vec2.w; return v * (1023. / 65535.); } @@ -485,7 +486,7 @@ float4 PSPlanar420_PQ_Reverse(VertTexPos frag_in) : TARGET float cr = image2.Load(xy0_chroma).x; float3 yuv = float3(y, cb, cr); float3 pq = YUV_to_RGB(yuv); - float3 hdr2020 = st2084_to_linear(pq) * maximum_over_sdr_white_nits; + float3 hdr2020 = st2084_to_linear_eetf(pq, hdr_lw, hdr_lmax) * maximum_over_sdr_white_nits; float3 rgb = rec2020_to_rec709(hdr2020); return float4(rgb, 1.); } @@ -628,7 +629,7 @@ float4 PSI010_PQ_2020_709_Reverse(VertTexPos frag_in) : TARGET float cr = image2.Load(xy0_chroma).x * ratio; float3 yuv = float3(y, cb, cr); float3 pq = YUV_to_RGB(yuv); - float3 hdr2020 = st2084_to_linear(pq) * maximum_over_sdr_white_nits; + float3 hdr2020 = st2084_to_linear_eetf(pq, hdr_lw, hdr_lmax) * maximum_over_sdr_white_nits; float3 rgb = rec2020_to_rec709(hdr2020); return float4(rgb, 1.); } @@ -667,7 +668,7 @@ float4 PSP010_PQ_2020_709_Reverse(VertTexPos frag_in) : TARGET float3 yuv_1023 = floor(yuv_65535 * 0.015625); float3 yuv = yuv_1023 / 1023.; float3 pq = YUV_to_RGB(yuv); - float3 hdr2020 = st2084_to_linear(pq) * maximum_over_sdr_white_nits; + float3 hdr2020 = st2084_to_linear_eetf(pq, hdr_lw, hdr_lmax) * maximum_over_sdr_white_nits; float3 rgb = rec2020_to_rec709(hdr2020); return float4(rgb, 1.); } diff --git a/libobs/obs-source.c b/libobs/obs-source.c index ff1648379..2dddba7fb 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -2243,6 +2243,9 @@ static bool update_async_texrender(struct obs_source *source, const float hlg_exponent = 0.2f + (0.42f * log10f(hlg_peak_level / 1000.f)); set_eparam(conv, "hlg_exponent", hlg_exponent); + set_eparam(conv, "hdr_lw", (float)frame->max_luminance); + set_eparam(conv, "hdr_lmax", + obs_get_video_hdr_nominal_peak_level()); struct vec4 vec0, vec1, vec2; vec4_set(&vec0, frame->color_matrix[0], frame->color_matrix[1], @@ -3228,6 +3231,7 @@ static void copy_frame_data(struct obs_source_frame *dst, dst->flags = src->flags; dst->trc = src->trc; dst->full_range = src->full_range; + dst->max_luminance = src->max_luminance; dst->timestamp = src->timestamp; memcpy(dst->color_matrix, src->color_matrix, sizeof(float) * 16); if (!dst->full_range) { @@ -3467,7 +3471,7 @@ void obs_source_output_video2(obs_source_t *source, return; } - struct obs_source_frame new_frame; + struct obs_source_frame new_frame = {0}; enum video_range_type range = resolve_video_range(frame->format, frame->range); @@ -3481,6 +3485,7 @@ void obs_source_output_video2(obs_source_t *source, new_frame.timestamp = frame->timestamp; new_frame.format = frame->format; new_frame.full_range = range == VIDEO_RANGE_FULL; + new_frame.max_luminance = 0; new_frame.flip = frame->flip; new_frame.flags = frame->flags; new_frame.trc = frame->trc; @@ -3608,7 +3613,7 @@ void obs_source_preload_video2(obs_source_t *source, return; } - struct obs_source_frame new_frame; + struct obs_source_frame new_frame = {0}; enum video_range_type range = resolve_video_range(frame->format, frame->range); @@ -3622,6 +3627,7 @@ void obs_source_preload_video2(obs_source_t *source, new_frame.timestamp = frame->timestamp; new_frame.format = frame->format; new_frame.full_range = range == VIDEO_RANGE_FULL; + new_frame.max_luminance = 0; new_frame.flip = frame->flip; new_frame.flags = frame->flags; new_frame.trc = frame->trc; @@ -3719,7 +3725,7 @@ void obs_source_set_video_frame2(obs_source_t *source, return; } - struct obs_source_frame new_frame; + struct obs_source_frame new_frame = {0}; enum video_range_type range = resolve_video_range(frame->format, frame->range); @@ -3733,6 +3739,7 @@ void obs_source_set_video_frame2(obs_source_t *source, new_frame.timestamp = frame->timestamp; new_frame.format = frame->format; new_frame.full_range = range == VIDEO_RANGE_FULL; + new_frame.max_luminance = 0; new_frame.flip = frame->flip; new_frame.flags = frame->flags; new_frame.trc = frame->trc; diff --git a/libobs/obs-video.c b/libobs/obs-video.c index f621bd664..a2dc38a6f 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -316,7 +316,7 @@ static void render_convert_texture(struct obs_core_video *video, gs_eparam_t *height_i = gs_effect_get_param_by_name(effect, "height_i"); gs_eparam_t *sdr_white_nits_over_maximum = gs_effect_get_param_by_name( effect, "sdr_white_nits_over_maximum"); - gs_eparam_t *hlg_lw = gs_effect_get_param_by_name(effect, "hlg_lw"); + gs_eparam_t *hdr_lw = gs_effect_get_param_by_name(effect, "hdr_lw"); struct vec4 vec0, vec1, vec2; vec4_set(&vec0, video->color_matrix[4], video->color_matrix[5], @@ -336,7 +336,7 @@ static void render_convert_texture(struct obs_core_video *video, gs_effect_set_texture(image, texture); gs_effect_set_vec4(color_vec0, &vec0); gs_effect_set_float(sdr_white_nits_over_maximum, multiplier); - gs_effect_set_float(hlg_lw, hdr_nominal_peak_level); + gs_effect_set_float(hdr_lw, hdr_nominal_peak_level); render_convert_plane(effect, convert_textures[0], video->conversion_techs[0]); @@ -350,7 +350,7 @@ static void render_convert_texture(struct obs_core_video *video, video->conversion_height_i); gs_effect_set_float(sdr_white_nits_over_maximum, multiplier); - gs_effect_set_float(hlg_lw, hdr_nominal_peak_level); + gs_effect_set_float(hdr_lw, hdr_nominal_peak_level); render_convert_plane(effect, convert_textures[1], video->conversion_techs[1]); @@ -363,7 +363,7 @@ static void render_convert_texture(struct obs_core_video *video, video->conversion_height_i); gs_effect_set_float(sdr_white_nits_over_maximum, multiplier); - gs_effect_set_float(hlg_lw, + gs_effect_set_float(hdr_lw, hdr_nominal_peak_level); render_convert_plane( effect, convert_textures[2], diff --git a/libobs/obs.h b/libobs/obs.h index 93b1e2450..bcb6e6ebe 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -270,6 +270,7 @@ struct obs_source_frame { enum video_format format; float color_matrix[16]; bool full_range; + uint16_t max_luminance; float color_range_min[3]; float color_range_max[3]; bool flip;