浏览代码

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.
jpark37 3 年之前
父节点
当前提交
11da542a0d
共有 6 个文件被更改,包括 64 次插入36 次删除
  1. 1 0
      docs/sphinx/reference-sources.rst
  2. 38 20
      libobs/data/color.effect
  3. 10 9
      libobs/data/format_conversion.effect
  4. 10 3
      libobs/obs-source.c
  5. 4 4
      libobs/obs-video.c
  6. 1 0
      libobs/obs.h

+ 1 - 0
docs/sphinx/reference-sources.rst

@@ -1317,6 +1317,7 @@ Functions used by sources
            enum video_format   format;
            enum video_format   format;
            float               color_matrix[16];
            float               color_matrix[16];
            bool                full_range;
            bool                full_range;
+           uint16_t            max_luminance;
            float               color_range_min[3];
            float               color_range_min[3];
            float               color_range_max[3];
            float               color_range_max[3];
            bool                flip;
            bool                flip;

+ 38 - 20
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);
 	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 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 KS = (1.5 * maxLum) - 0.5;
 	float E2 = E1;
 	float E2 = E1;
 	if (E1 > KS)
 	if (E1 > KS)
@@ -93,18 +86,43 @@ float eetf_0_1000(float Lw, float maxRGB1_pq)
 	return E4;
 	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);
 	float maxRGB2_linear = st2084_to_linear_channel(maxRGB2_pq);
 
 
 	// avoid divide-by-zero possibility
 	// 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)
 float3 linear_to_hlg(float3 rgb, float Lw)
@@ -113,7 +131,7 @@ float3 linear_to_hlg(float3 rgb, float Lw)
 
 
 	if (Lw > 1000.)
 	if (Lw > 1000.)
 	{
 	{
-		rgb = maxRGB_eetf(rgb, Lw, 1000.);
+		rgb = maxRGB_eetf_linear_to_linear(rgb, Lw, 1000.);
 		rgb *= 10000. / Lw;
 		rgb *= 10000. / Lw;
 	}
 	}
 	else
 	else

+ 10 - 9
libobs/data/format_conversion.effect

@@ -27,7 +27,8 @@ uniform float     width_x2_i;
 uniform float     maximum_over_sdr_white_nits;
 uniform float     maximum_over_sdr_white_nits;
 uniform float     sdr_white_nits_over_maximum;
 uniform float     sdr_white_nits_over_maximum;
 uniform float     hlg_exponent;
 uniform float     hlg_exponent;
-uniform float     hlg_lw;
+uniform float     hdr_lw;
+uniform float     hdr_lmax;
 
 
 uniform float4    color_vec0;
 uniform float4    color_vec0;
 uniform float4    color_vec1;
 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;
 	float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb * sdr_white_nits_over_maximum;
 	rgb = rec709_to_rec2020(rgb);
 	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;
 	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
 	y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits
 	y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits
 	return y;
 	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;
 	float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb * sdr_white_nits_over_maximum;
 	rgb = rec709_to_rec2020(rgb);
 	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;
 	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
 	return y * (1023. / 65535.);
 	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_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);
 	float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum);
 	rgb = rec709_to_rec2020(rgb);
 	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 u = dot(color_vec1.xyz, rgb) + color_vec1.w;
 	float v = dot(color_vec2.xyz, rgb) + color_vec2.w;
 	float v = dot(color_vec2.xyz, rgb) + color_vec2.w;
 	float2 uv = float2(u, v);
 	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_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);
 	float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum);
 	rgb = rec709_to_rec2020(rgb);
 	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 u = dot(color_vec1.xyz, rgb) + color_vec1.w;
 	return u * (1023. / 65535.);
 	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_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);
 	float3 rgb = (rgb_topleft + rgb_topright + rgb_bottomleft + rgb_bottomright) * (0.25 * sdr_white_nits_over_maximum);
 	rgb = rec709_to_rec2020(rgb);
 	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;
 	float v = dot(color_vec2.xyz, rgb) + color_vec2.w;
 	return v * (1023. / 65535.);
 	return v * (1023. / 65535.);
 }
 }
@@ -485,7 +486,7 @@ float4 PSPlanar420_PQ_Reverse(VertTexPos frag_in) : TARGET
 	float cr = image2.Load(xy0_chroma).x;
 	float cr = image2.Load(xy0_chroma).x;
 	float3 yuv = float3(y, cb, cr);
 	float3 yuv = float3(y, cb, cr);
 	float3 pq = YUV_to_RGB(yuv);
 	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);
 	float3 rgb = rec2020_to_rec709(hdr2020);
 	return float4(rgb, 1.);
 	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;
 	float cr = image2.Load(xy0_chroma).x * ratio;
 	float3 yuv = float3(y, cb, cr);
 	float3 yuv = float3(y, cb, cr);
 	float3 pq = YUV_to_RGB(yuv);
 	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);
 	float3 rgb = rec2020_to_rec709(hdr2020);
 	return float4(rgb, 1.);
 	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_1023 = floor(yuv_65535 * 0.015625);
 	float3 yuv = yuv_1023 / 1023.;
 	float3 yuv = yuv_1023 / 1023.;
 	float3 pq = YUV_to_RGB(yuv);
 	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);
 	float3 rgb = rec2020_to_rec709(hdr2020);
 	return float4(rgb, 1.);
 	return float4(rgb, 1.);
 }
 }

+ 10 - 3
libobs/obs-source.c

@@ -2243,6 +2243,9 @@ static bool update_async_texrender(struct obs_source *source,
 		const float hlg_exponent =
 		const float hlg_exponent =
 			0.2f + (0.42f * log10f(hlg_peak_level / 1000.f));
 			0.2f + (0.42f * log10f(hlg_peak_level / 1000.f));
 		set_eparam(conv, "hlg_exponent", hlg_exponent);
 		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;
 		struct vec4 vec0, vec1, vec2;
 		vec4_set(&vec0, frame->color_matrix[0], frame->color_matrix[1],
 		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->flags = src->flags;
 	dst->trc = src->trc;
 	dst->trc = src->trc;
 	dst->full_range = src->full_range;
 	dst->full_range = src->full_range;
+	dst->max_luminance = src->max_luminance;
 	dst->timestamp = src->timestamp;
 	dst->timestamp = src->timestamp;
 	memcpy(dst->color_matrix, src->color_matrix, sizeof(float) * 16);
 	memcpy(dst->color_matrix, src->color_matrix, sizeof(float) * 16);
 	if (!dst->full_range) {
 	if (!dst->full_range) {
@@ -3467,7 +3471,7 @@ void obs_source_output_video2(obs_source_t *source,
 		return;
 		return;
 	}
 	}
 
 
-	struct obs_source_frame new_frame;
+	struct obs_source_frame new_frame = {0};
 	enum video_range_type range =
 	enum video_range_type range =
 		resolve_video_range(frame->format, frame->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.timestamp = frame->timestamp;
 	new_frame.format = frame->format;
 	new_frame.format = frame->format;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
+	new_frame.max_luminance = 0;
 	new_frame.flip = frame->flip;
 	new_frame.flip = frame->flip;
 	new_frame.flags = frame->flags;
 	new_frame.flags = frame->flags;
 	new_frame.trc = frame->trc;
 	new_frame.trc = frame->trc;
@@ -3608,7 +3613,7 @@ void obs_source_preload_video2(obs_source_t *source,
 		return;
 		return;
 	}
 	}
 
 
-	struct obs_source_frame new_frame;
+	struct obs_source_frame new_frame = {0};
 	enum video_range_type range =
 	enum video_range_type range =
 		resolve_video_range(frame->format, frame->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.timestamp = frame->timestamp;
 	new_frame.format = frame->format;
 	new_frame.format = frame->format;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
+	new_frame.max_luminance = 0;
 	new_frame.flip = frame->flip;
 	new_frame.flip = frame->flip;
 	new_frame.flags = frame->flags;
 	new_frame.flags = frame->flags;
 	new_frame.trc = frame->trc;
 	new_frame.trc = frame->trc;
@@ -3719,7 +3725,7 @@ void obs_source_set_video_frame2(obs_source_t *source,
 		return;
 		return;
 	}
 	}
 
 
-	struct obs_source_frame new_frame;
+	struct obs_source_frame new_frame = {0};
 	enum video_range_type range =
 	enum video_range_type range =
 		resolve_video_range(frame->format, frame->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.timestamp = frame->timestamp;
 	new_frame.format = frame->format;
 	new_frame.format = frame->format;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
 	new_frame.full_range = range == VIDEO_RANGE_FULL;
+	new_frame.max_luminance = 0;
 	new_frame.flip = frame->flip;
 	new_frame.flip = frame->flip;
 	new_frame.flags = frame->flags;
 	new_frame.flags = frame->flags;
 	new_frame.trc = frame->trc;
 	new_frame.trc = frame->trc;

+ 4 - 4
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 *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(
 	gs_eparam_t *sdr_white_nits_over_maximum = gs_effect_get_param_by_name(
 		effect, "sdr_white_nits_over_maximum");
 		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;
 	struct vec4 vec0, vec1, vec2;
 	vec4_set(&vec0, video->color_matrix[4], video->color_matrix[5],
 	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_texture(image, texture);
 		gs_effect_set_vec4(color_vec0, &vec0);
 		gs_effect_set_vec4(color_vec0, &vec0);
 		gs_effect_set_float(sdr_white_nits_over_maximum, multiplier);
 		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],
 		render_convert_plane(effect, convert_textures[0],
 				     video->conversion_techs[0]);
 				     video->conversion_techs[0]);
 
 
@@ -350,7 +350,7 @@ static void render_convert_texture(struct obs_core_video *video,
 					    video->conversion_height_i);
 					    video->conversion_height_i);
 			gs_effect_set_float(sdr_white_nits_over_maximum,
 			gs_effect_set_float(sdr_white_nits_over_maximum,
 					    multiplier);
 					    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],
 			render_convert_plane(effect, convert_textures[1],
 					     video->conversion_techs[1]);
 					     video->conversion_techs[1]);
 
 
@@ -363,7 +363,7 @@ static void render_convert_texture(struct obs_core_video *video,
 						    video->conversion_height_i);
 						    video->conversion_height_i);
 				gs_effect_set_float(sdr_white_nits_over_maximum,
 				gs_effect_set_float(sdr_white_nits_over_maximum,
 						    multiplier);
 						    multiplier);
-				gs_effect_set_float(hlg_lw,
+				gs_effect_set_float(hdr_lw,
 						    hdr_nominal_peak_level);
 						    hdr_nominal_peak_level);
 				render_convert_plane(
 				render_convert_plane(
 					effect, convert_textures[2],
 					effect, convert_textures[2],

+ 1 - 0
libobs/obs.h

@@ -270,6 +270,7 @@ struct obs_source_frame {
 	enum video_format format;
 	enum video_format format;
 	float color_matrix[16];
 	float color_matrix[16];
 	bool full_range;
 	bool full_range;
+	uint16_t max_luminance;
 	float color_range_min[3];
 	float color_range_min[3];
 	float color_range_max[3];
 	float color_range_max[3];
 	bool flip;
 	bool flip;