浏览代码

libobs: Add high-precision sRGB support

jpark37 3 年之前
父节点
当前提交
06111d5b10

+ 1 - 0
docs/sphinx/reference-libobs-graphics-graphics.rst

@@ -52,6 +52,7 @@ Graphics Enumerations
    Color space.  Can be one of the following values:
 
    - GS_CS_SRGB         - sRGB
+   - GS_CS_SRGB_16F     - High-precision SDR
    - GS_CS_709_EXTENDED - Canvas, Mac EDR (HDR)
    - GS_CS_709_SCRGB    - 1.0 = 80 nits, Windows/Linux HDR
 

+ 19 - 0
libobs/data/default.effect

@@ -45,6 +45,16 @@ float4 PSDrawNonlinearAlpha(VertInOut vert_in) : TARGET
 	return rgba;
 }
 
+float4 PSDrawNonlinearAlphaMultiply(VertInOut vert_in) : TARGET
+{
+	float4 rgba = image.Sample(def_sampler, vert_in.uv);
+	rgba.rgb = srgb_linear_to_nonlinear(rgba.rgb);
+	rgba.rgb *= rgba.a;
+	rgba.rgb = srgb_nonlinear_to_linear(rgba.rgb);
+	rgba.rgb *= multiplier;
+	return rgba;
+}
+
 float4 PSDrawSrgbDecompress(VertInOut vert_in) : TARGET
 {
 	float4 rgba = image.Sample(def_sampler, vert_in.uv);
@@ -105,6 +115,15 @@ technique DrawNonlinearAlpha
 	}
 }
 
+technique DrawNonlinearAlphaMultiply
+{
+	pass
+	{
+		vertex_shader = VSDefault(vert_in);
+		pixel_shader  = PSDrawNonlinearAlphaMultiply(vert_in);
+	}
+}
+
 technique DrawSrgbDecompress
 {
 	pass

+ 146 - 4
libobs/data/format_conversion.effect

@@ -205,7 +205,7 @@ float PS_PQ_Y_709_2020(FragPos frag_in) : TARGET
 	rgb = rec709_to_rec2020(rgb);
 	rgb = linear_to_st2084(rgb);
 	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
-	y = (65472. / 65535.) * y + 0.00048828125; // set up truncation to 10 bits
+	y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits
 	return y;
 }
 
@@ -215,7 +215,16 @@ float PS_HLG_Y_709_2020(FragPos frag_in) : TARGET
 	rgb = rec709_to_rec2020(rgb);
 	rgb = linear_to_hlg(rgb);
 	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
-	y = (65472. / 65535.) * y + 0.00048828125; // set up truncation to 10 bits
+	y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits
+	return y;
+}
+
+float PS_SRGB_Y(FragPos frag_in) : TARGET
+{
+	float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb;
+	rgb = srgb_linear_to_nonlinear(rgb);
+	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
+	y = (65472. / 65535.) * y + (32. / 65535.); // set up truncation to 10 bits
 	return y;
 }
 
@@ -237,6 +246,14 @@ float PS_I010_HLG_Y_709_2020(FragPos frag_in) : TARGET
 	return y * (1023. / 65535.);
 }
 
+float PS_I010_SRGB_Y(FragPos frag_in) : TARGET
+{
+	float3 rgb = image.Load(int3(frag_in.pos.xy, 0)).rgb;
+	rgb = srgb_linear_to_nonlinear(rgb);
+	float y = dot(color_vec0.xyz, rgb) + color_vec0.w;
+	return y * (1023. / 65535.);
+}
+
 float2 PS_UV_Wide(FragTexWide frag_in) : TARGET
 {
 	float3 rgb_left = image.Sample(def_sampler, frag_in.uuv.xz).rgb;
@@ -259,7 +276,7 @@ float2 PS_PQ_UV_709_2020_WideWide(FragTexWideWide frag_in) : TARGET
 	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);
-	uv = (65472. / 65535.) * uv + 0.00048828125; // set up truncation to 10 bits
+	uv = (65472. / 65535.) * uv + (32. / 65535.); // set up truncation to 10 bits
 	return uv;
 }
 
@@ -275,7 +292,22 @@ float2 PS_HLG_UV_709_2020_WideWide(FragTexWideWide frag_in) : TARGET
 	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);
-	uv = (65472. / 65535.) * uv + 0.00048828125; // set up truncation to 10 bits
+	uv = (65472. / 65535.) * uv + (32. / 65535.); // set up truncation to 10 bits
+	return uv;
+}
+
+float2 PS_SRGB_UV_WideWide(FragTexWideWide frag_in) : TARGET
+{
+	float3 rgb_topleft = image.Sample(def_sampler, frag_in.uuvv.xz).rgb;
+	float3 rgb_topright = image.Sample(def_sampler, frag_in.uuvv.yz).rgb;
+	float3 rgb_bottomleft = image.Sample(def_sampler, frag_in.uuvv.xw).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;
+	rgb = srgb_linear_to_nonlinear(rgb);
+	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);
+	uv = (65472. / 65535.) * uv + (32. / 65535.); // set up truncation to 10 bits
 	return uv;
 }
 
@@ -337,6 +369,18 @@ float PS_I010_HLG_U_709_2020_WideWide(FragTexWideWide frag_in) : TARGET
 	return u * (1023. / 65535.);
 }
 
+float PS_I010_SRGB_U_WideWide(FragTexWideWide frag_in) : TARGET
+{
+	float3 rgb_topleft = image.Sample(def_sampler, frag_in.uuvv.xz).rgb;
+	float3 rgb_topright = image.Sample(def_sampler, frag_in.uuvv.yz).rgb;
+	float3 rgb_bottomleft = image.Sample(def_sampler, frag_in.uuvv.xw).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;
+	rgb = srgb_linear_to_nonlinear(rgb);
+	float u = dot(color_vec1.xyz, rgb) + color_vec1.w;
+	return u * (1023. / 65535.);
+}
+
 float PS_I010_PQ_V_709_2020_WideWide(FragTexWideWide frag_in) : TARGET
 {
 	float3 rgb_topleft = image.Sample(def_sampler, frag_in.uuvv.xz).rgb;
@@ -363,6 +407,18 @@ float PS_I010_HLG_V_709_2020_WideWide(FragTexWideWide frag_in) : TARGET
 	return v * (1023. / 65535.);
 }
 
+float PS_I010_SRGB_V_WideWide(FragTexWideWide frag_in) : TARGET
+{
+	float3 rgb_topleft = image.Sample(def_sampler, frag_in.uuvv.xz).rgb;
+	float3 rgb_topright = image.Sample(def_sampler, frag_in.uuvv.yz).rgb;
+	float3 rgb_bottomleft = image.Sample(def_sampler, frag_in.uuvv.xw).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;
+	rgb = srgb_linear_to_nonlinear(rgb);
+	float v = dot(color_vec2.xyz, rgb) + color_vec2.w;
+	return v * (1023. / 65535.);
+}
+
 float3 YUV_to_RGB(float3 yuv)
 {
 	yuv = clamp(yuv, color_range_min, color_range_max);
@@ -495,6 +551,19 @@ float3 PSNV12_Reverse(VertTexPos frag_in) : TARGET
 	return rgb;
 }
 
+float4 PSI010_SRGB_Reverse(VertTexPos frag_in) : TARGET
+{
+	float ratio = 65535. / 1023.;
+	float y = image.Load(int3(frag_in.pos.xy, 0)).x * ratio;
+	int3 xy0_chroma = int3(frag_in.uv, 0);
+	float cb = image1.Load(xy0_chroma).x * ratio;
+	float cr = image2.Load(xy0_chroma).x * ratio;
+	float3 yuv = float3(y, cb, cr);
+	float3 rgb = YUV_to_RGB(yuv);
+	rgb = srgb_nonlinear_to_linear(rgb);
+	return float4(rgb, 1.0);
+}
+
 float4 PSI010_PQ_2020_709_Reverse(VertTexPos frag_in) : TARGET
 {
 	float ratio = 65535. / 1023.;
@@ -523,6 +592,16 @@ float4 PSI010_HLG_2020_709_Reverse(VertTexPos frag_in) : TARGET
 	return float4(rgb, 1.0);
 }
 
+float4 PSP010_SRGB_Reverse(VertTexPos frag_in) : TARGET
+{
+	float y = image.Load(int3(frag_in.pos.xy, 0)).x;
+	float2 cbcr = image1.Load(int3(frag_in.uv, 0)).xy;
+	float3 yuv = float3(y, cbcr);
+	float3 rgb = YUV_to_RGB(yuv);
+	rgb = srgb_nonlinear_to_linear(rgb);
+	return float4(rgb, 1.0);
+}
+
 float4 PSP010_PQ_2020_709_Reverse(VertTexPos frag_in) : TARGET
 {
 	float y = image.Load(int3(frag_in.pos.xy, 0)).x;
@@ -669,6 +748,15 @@ technique I010_HLG_Y
 	}
 }
 
+technique I010_SRGB_Y
+{
+	pass
+	{
+		vertex_shader = VSPos(id);
+		pixel_shader  = PS_I010_SRGB_Y(frag_in);
+	}
+}
+
 technique I010_PQ_U
 {
 	pass
@@ -687,6 +775,15 @@ technique I010_HLG_U
 	}
 }
 
+technique I010_SRGB_U
+{
+	pass
+	{
+		vertex_shader = VSTexPos_TopLeft(id);
+		pixel_shader  = PS_I010_SRGB_U_WideWide(frag_in);
+	}
+}
+
 technique I010_PQ_V
 {
 	pass
@@ -705,6 +802,15 @@ technique I010_HLG_V
 	}
 }
 
+technique I010_SRGB_V
+{
+	pass
+	{
+		vertex_shader = VSTexPos_TopLeft(id);
+		pixel_shader  = PS_I010_SRGB_V_WideWide(frag_in);
+	}
+}
+
 technique P010_PQ_Y
 {
 	pass
@@ -723,6 +829,15 @@ technique P010_HLG_Y
 	}
 }
 
+technique P010_SRGB_Y
+{
+	pass
+	{
+		vertex_shader = VSPos(id);
+		pixel_shader  = PS_SRGB_Y(frag_in);
+	}
+}
+
 technique P010_PQ_UV
 {
 	pass
@@ -741,6 +856,15 @@ technique P010_HLG_UV
 	}
 }
 
+technique P010_SRGB_UV
+{
+	pass
+	{
+		vertex_shader = VSTexPos_TopLeft(id);
+		pixel_shader  = PS_SRGB_UV_WideWide(frag_in);
+	}
+}
+
 technique UYVY_Reverse
 {
 	pass
@@ -840,6 +964,15 @@ technique NV12_Reverse
 	}
 }
 
+technique I010_SRGB_Reverse
+{
+	pass
+	{
+		vertex_shader = VSTexPosHalfHalf_Reverse(id);
+		pixel_shader  = PSI010_SRGB_Reverse(frag_in);
+	}
+}
+
 technique I010_PQ_2020_709_Reverse
 {
 	pass
@@ -858,6 +991,15 @@ technique I010_HLG_2020_709_Reverse
 	}
 }
 
+technique P010_SRGB_Reverse
+{
+	pass
+	{
+		vertex_shader = VSTexPosHalfHalf_Reverse(id);
+		pixel_shader  = PSP010_SRGB_Reverse(frag_in);
+	}
+}
+
 technique P010_PQ_2020_709_Reverse
 {
 	pass

+ 2 - 0
libobs/graphics/graphics.h

@@ -81,6 +81,7 @@ enum gs_color_format {
 
 enum gs_color_space {
 	GS_CS_SRGB,         /* SDR */
+	GS_CS_SRGB_16F,     /* High-precision SDR */
 	GS_CS_709_EXTENDED, /* Canvas, Mac EDR (HDR) */
 	GS_CS_709_SCRGB,    /* 1.0 = 80 nits, Windows/Linux HDR */
 };
@@ -1061,6 +1062,7 @@ gs_get_format_from_space(enum gs_color_space space)
 	switch (space) {
 	case GS_CS_SRGB:
 		break;
+	case GS_CS_SRGB_16F:
 	case GS_CS_709_EXTENDED:
 	case GS_CS_709_SCRGB:
 		return GS_RGBA16F;

+ 5 - 5
libobs/obs-internal.h

@@ -898,14 +898,14 @@ convert_video_format(enum video_format format)
 }
 
 static inline enum gs_color_space
-convert_video_space(enum video_format format, size_t count,
+convert_video_space(enum video_format format, enum video_trc trc,
+		    enum gs_color_format color_format, size_t count,
 		    const enum gs_color_space *preferred_spaces)
 {
 	enum gs_color_space video_space = GS_CS_SRGB;
-	switch (format) {
-	case VIDEO_FORMAT_I010:
-	case VIDEO_FORMAT_P010:
-		video_space = GS_CS_709_EXTENDED;
+	if (color_format == GS_RGBA16F) {
+		video_space = (trc == VIDEO_TRC_SRGB) ? GS_CS_SRGB_16F
+						      : GS_CS_709_EXTENDED;
 	}
 
 	enum gs_color_space space = video_space;

+ 8 - 0
libobs/obs-scene.c

@@ -612,6 +612,7 @@ static void render_item_texture(struct obs_scene_item *item,
 	case GS_CS_709_SCRGB:
 		switch (source_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 		case GS_CS_709_EXTENDED:
 			multiplier = obs_get_video_sdr_white_level() / 80.f;
 		}
@@ -621,6 +622,7 @@ static void render_item_texture(struct obs_scene_item *item,
 	case GS_CS_709_SCRGB:
 		switch (current_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 		case GS_CS_709_EXTENDED:
 			multiplier = 80.f / obs_get_video_sdr_white_level();
 		}
@@ -631,6 +633,7 @@ static void render_item_texture(struct obs_scene_item *item,
 		tech_name = "DrawUpscale";
 		switch (source_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			switch (current_space) {
 			case GS_CS_709_SCRGB:
 				tech_name = "DrawUpscaleMultiply";
@@ -639,6 +642,7 @@ static void render_item_texture(struct obs_scene_item *item,
 		case GS_CS_709_EXTENDED:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawUpscaleTonemap";
 				break;
 			case GS_CS_709_SCRGB:
@@ -648,6 +652,7 @@ static void render_item_texture(struct obs_scene_item *item,
 		case GS_CS_709_SCRGB:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawUpscaleMultiplyTonemap";
 				break;
 			case GS_CS_709_EXTENDED:
@@ -657,6 +662,7 @@ static void render_item_texture(struct obs_scene_item *item,
 	} else {
 		switch (source_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			switch (current_space) {
 			case GS_CS_709_SCRGB:
 				tech_name = "DrawMultiply";
@@ -665,6 +671,7 @@ static void render_item_texture(struct obs_scene_item *item,
 		case GS_CS_709_EXTENDED:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawTonemap";
 				break;
 			case GS_CS_709_SCRGB:
@@ -674,6 +681,7 @@ static void render_item_texture(struct obs_scene_item *item,
 		case GS_CS_709_SCRGB:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawMultiplyTonemap";
 				break;
 			case GS_CS_709_EXTENDED:

+ 10 - 3
libobs/obs-source-deinterlace.c

@@ -393,9 +393,13 @@ void deinterlace_render(obs_source_t *s)
 	if (!cur_tex || !prev_tex || !s->async_width || !s->async_height)
 		return;
 
-	const enum gs_color_space source_space =
-		(s->async_color_format == GS_RGBA16F) ? GS_CS_709_EXTENDED
-						      : GS_CS_SRGB;
+	enum gs_color_space source_space = GS_CS_SRGB;
+	if (s->async_color_format == GS_RGBA16F) {
+		source_space = (s->async_trc == VIDEO_TRC_SRGB)
+				       ? GS_CS_SRGB_16F
+				       : GS_CS_709_EXTENDED;
+	}
+
 	const bool linear_srgb =
 		(source_space != GS_CS_SRGB) || gs_get_linear_srgb() ||
 		deinterlace_linear_required(s->deinterlace_mode);
@@ -405,6 +409,7 @@ void deinterlace_render(obs_source_t *s)
 	float multiplier = 1.0;
 	switch (source_space) {
 	case GS_CS_SRGB:
+	case GS_CS_SRGB_16F:
 		switch (current_space) {
 		case GS_CS_709_SCRGB:
 			tech_name = "DrawMultiply";
@@ -414,6 +419,7 @@ void deinterlace_render(obs_source_t *s)
 	case GS_CS_709_EXTENDED:
 		switch (current_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			tech_name = "DrawTonemap";
 			break;
 		case GS_CS_709_SCRGB:
@@ -424,6 +430,7 @@ void deinterlace_render(obs_source_t *s)
 	case GS_CS_709_SCRGB:
 		switch (current_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			tech_name = "DrawMultiplyTonemap";
 			multiplier = 80.0f / obs_get_video_sdr_white_level();
 			break;

+ 20 - 15
libobs/obs-source-transition.c

@@ -832,12 +832,16 @@ void obs_transition_video_render2(
 static enum gs_color_space mix_spaces(enum gs_color_space a,
 				      enum gs_color_space b)
 {
-	assert((a == GS_CS_SRGB) || (a == GS_CS_709_EXTENDED));
-	assert((b == GS_CS_SRGB) || (b == GS_CS_709_EXTENDED));
-
-	return ((a == GS_CS_709_EXTENDED) || (b == GS_CS_709_EXTENDED))
-		       ? GS_CS_709_EXTENDED
-		       : GS_CS_SRGB;
+	assert((a == GS_CS_SRGB) || (a == GS_CS_SRGB_16F) ||
+	       (a == GS_CS_709_EXTENDED));
+	assert((b == GS_CS_SRGB) || (b == GS_CS_SRGB_16F) ||
+	       (b == GS_CS_709_EXTENDED));
+
+	if ((a == GS_CS_709_EXTENDED) || (b == GS_CS_709_EXTENDED))
+		return GS_CS_709_EXTENDED;
+	if ((a == GS_CS_SRGB_16F) || (b == GS_CS_SRGB_16F))
+		return GS_CS_SRGB_16F;
+	return GS_CS_SRGB;
 }
 
 enum gs_color_space
@@ -846,25 +850,26 @@ obs_transition_video_get_color_space(obs_source_t *transition)
 	obs_source_t *source0 = transition->transition_sources[0];
 	obs_source_t *source1 = transition->transition_sources[1];
 
-	const enum gs_color_space dual_spaces[] = {
+	const enum gs_color_space preferred_spaces[] = {
 		GS_CS_SRGB,
+		GS_CS_SRGB_16F,
 		GS_CS_709_EXTENDED,
 	};
 
 	enum gs_color_space space = GS_CS_SRGB;
 
 	if (source0) {
-		space = mix_spaces(space,
-				   obs_source_get_color_space(
-					   source0, OBS_COUNTOF(dual_spaces),
-					   dual_spaces));
+		space = mix_spaces(space, obs_source_get_color_space(
+						  source0,
+						  OBS_COUNTOF(preferred_spaces),
+						  preferred_spaces));
 	}
 
 	if (source1) {
-		space = mix_spaces(space,
-				   obs_source_get_color_space(
-					   source1, OBS_COUNTOF(dual_spaces),
-					   dual_spaces));
+		space = mix_spaces(space, obs_source_get_color_space(
+						  source1,
+						  OBS_COUNTOF(preferred_spaces),
+						  preferred_spaces));
 	}
 
 	return space;

+ 83 - 61
libobs/obs-source.c

@@ -1550,8 +1550,10 @@ enum convert_type {
 	CONVERT_800,
 	CONVERT_RGB_LIMITED,
 	CONVERT_BGR3,
+	CONVERT_I010_SRGB,
 	CONVERT_I010_PQ_2020_709,
 	CONVERT_I010_HLG_2020_709,
+	CONVERT_P010_SRGB,
 	CONVERT_P010_PQ_2020_709,
 	CONVERT_P010_HLG_2020_709,
 };
@@ -1599,15 +1601,25 @@ static inline enum convert_type get_convert_type(enum video_format format,
 		return CONVERT_444_A_PACK;
 
 	case VIDEO_FORMAT_I010: {
-		const bool hlg = trc == VIDEO_TRC_HLG;
-		return hlg ? CONVERT_I010_HLG_2020_709
-			   : CONVERT_I010_PQ_2020_709;
+		switch (trc) {
+		case VIDEO_TRC_SRGB:
+			return CONVERT_I010_SRGB;
+		case VIDEO_TRC_HLG:
+			return CONVERT_I010_HLG_2020_709;
+		default:
+			return CONVERT_I010_PQ_2020_709;
+		}
 	}
 
 	case VIDEO_FORMAT_P010: {
-		const bool hlg = trc == VIDEO_TRC_HLG;
-		return hlg ? CONVERT_P010_HLG_2020_709
-			   : CONVERT_P010_PQ_2020_709;
+		switch (trc) {
+		case VIDEO_TRC_SRGB:
+			return CONVERT_P010_SRGB;
+		case VIDEO_TRC_HLG:
+			return CONVERT_P010_HLG_2020_709;
+		default:
+			return CONVERT_P010_PQ_2020_709;
+		}
 	}
 	}
 
@@ -1885,10 +1897,12 @@ static inline bool init_gpu_conversion(struct obs_source *source,
 	case CONVERT_444_A_PACK:
 		return set_packed444_alpha_sizes(source, frame);
 
+	case CONVERT_I010_SRGB:
 	case CONVERT_I010_PQ_2020_709:
 	case CONVERT_I010_HLG_2020_709:
 		return set_i010_sizes(source, frame);
 
+	case CONVERT_P010_SRGB:
 	case CONVERT_P010_PQ_2020_709:
 	case CONVERT_P010_HLG_2020_709:
 		return set_p010_sizes(source, frame);
@@ -1980,8 +1994,10 @@ static void upload_raw_frame(gs_texture_t *tex[MAX_AV_PLANES],
 	case CONVERT_422_A:
 	case CONVERT_444_A:
 	case CONVERT_444_A_PACK:
+	case CONVERT_I010_SRGB:
 	case CONVERT_I010_PQ_2020_709:
 	case CONVERT_I010_HLG_2020_709:
+	case CONVERT_P010_SRGB:
 	case CONVERT_P010_PQ_2020_709:
 	case CONVERT_P010_HLG_2020_709:
 		for (size_t c = 0; c < MAX_AV_PLANES; c++) {
@@ -2041,15 +2057,25 @@ static const char *select_conversion_technique(enum video_format format,
 		return "AYUV_Reverse";
 
 	case VIDEO_FORMAT_I010: {
-		const bool hlg = trc == VIDEO_TRC_HLG;
-		return hlg ? "I010_HLG_2020_709_Reverse"
-			   : "I010_PQ_2020_709_Reverse";
+		switch (trc) {
+		case VIDEO_TRC_SRGB:
+			return "I010_SRGB_Reverse";
+		case VIDEO_TRC_HLG:
+			return "I010_HLG_2020_709_Reverse";
+		default:
+			return "I010_PQ_2020_709_Reverse";
+		}
 	}
 
 	case VIDEO_FORMAT_P010: {
-		const bool hlg = trc == VIDEO_TRC_HLG;
-		return hlg ? "P010_HLG_2020_709_Reverse"
-			   : "P010_PQ_2020_709_Reverse";
+		switch (trc) {
+		case VIDEO_TRC_SRGB:
+			return "P010_SRGB_Reverse";
+		case VIDEO_TRC_HLG:
+			return "P010_HLG_2020_709_Reverse";
+		default:
+			return "P010_PQ_2020_709_Reverse";
+		}
 	}
 
 	case VIDEO_FORMAT_BGRA:
@@ -2240,42 +2266,6 @@ static inline void obs_source_draw_texture(struct obs_source *source,
 	gs_enable_framebuffer_srgb(previous);
 }
 
-static void obs_source_draw_async_texture(struct obs_source *source)
-{
-	gs_effect_t *effect = gs_get_effect();
-	bool def_draw = (!effect);
-	bool premultiplied = false;
-	gs_technique_t *tech = NULL;
-
-	if (def_draw) {
-		effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
-		const bool nonlinear_alpha = gs_get_linear_srgb() &&
-					     !source->async_linear_alpha;
-		const char *tech_name = nonlinear_alpha ? "DrawNonlinearAlpha"
-							: "Draw";
-		premultiplied = nonlinear_alpha;
-		tech = gs_effect_get_technique(effect, tech_name);
-		gs_technique_begin(tech);
-		gs_technique_begin_pass(tech, 0);
-	}
-
-	if (premultiplied) {
-		gs_blend_state_push();
-		gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
-	}
-
-	obs_source_draw_texture(source, effect);
-
-	if (premultiplied) {
-		gs_blend_state_pop();
-	}
-
-	if (def_draw) {
-		gs_technique_end_pass(tech);
-		gs_technique_end(tech);
-	}
-}
-
 static void recreate_async_texture(obs_source_t *source,
 				   enum gs_color_format format)
 {
@@ -2354,37 +2344,53 @@ static void rotate_async_video(obs_source_t *source, long rotation)
 static inline void obs_source_render_async_video(obs_source_t *source)
 {
 	if (source->async_textures[0] && source->async_active) {
+		enum gs_color_space source_space = GS_CS_SRGB;
+		if (source->async_color_format == GS_RGBA16F) {
+			source_space = (source->async_trc == VIDEO_TRC_SRGB)
+					       ? GS_CS_SRGB_16F
+					       : GS_CS_709_EXTENDED;
+		}
+
 		gs_effect_t *const effect =
 			obs_get_base_effect(OBS_EFFECT_DEFAULT);
 		const char *tech_name = "Draw";
 		float multiplier = 1.0;
-		const enum gs_color_space source_space =
-			(source->async_color_format == GS_RGBA16F)
-				? GS_CS_709_EXTENDED
-				: GS_CS_SRGB;
 		const enum gs_color_space current_space = gs_get_color_space();
-		bool linear_srgb = gs_get_linear_srgb();
+		const bool linear_srgb = gs_get_linear_srgb();
+		bool nonlinear_alpha = false;
 		switch (source_space) {
 		case GS_CS_SRGB:
+			nonlinear_alpha = linear_srgb &&
+					  !source->async_linear_alpha;
 			switch (current_space) {
 			case GS_CS_SRGB:
-				if (linear_srgb &&
-				    !source->async_linear_alpha) {
+			case GS_CS_SRGB_16F:
+			case GS_CS_709_EXTENDED:
+				if (nonlinear_alpha)
 					tech_name = "DrawNonlinearAlpha";
-				}
 				break;
+			case GS_CS_709_SCRGB:
+				tech_name =
+					nonlinear_alpha
+						? "DrawNonlinearAlphaMultiply"
+						: "DrawMultiply";
+				multiplier =
+					obs_get_video_sdr_white_level() / 80.0f;
+			}
+			break;
+		case GS_CS_SRGB_16F:
+			switch (current_space) {
 			case GS_CS_709_SCRGB:
 				tech_name = "DrawMultiply";
 				multiplier =
 					obs_get_video_sdr_white_level() / 80.0f;
-				linear_srgb = true;
 			}
 			break;
 		case GS_CS_709_EXTENDED:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawTonemap";
-				linear_srgb = true;
 				break;
 			case GS_CS_709_SCRGB:
 				tech_name = "DrawMultiply";
@@ -2395,10 +2401,10 @@ static inline void obs_source_render_async_video(obs_source_t *source)
 		case GS_CS_709_SCRGB:
 			switch (current_space) {
 			case GS_CS_SRGB:
+			case GS_CS_SRGB_16F:
 				tech_name = "DrawMultiplyTonemap";
 				multiplier =
 					80.0f / obs_get_video_sdr_white_level();
-				linear_srgb = true;
 				break;
 			case GS_CS_709_EXTENDED:
 				tech_name = "DrawMultiply";
@@ -2422,7 +2428,18 @@ static inline void obs_source_render_async_video(obs_source_t *source)
 			gs_matrix_push();
 			rotate_async_video(source, rotation);
 		}
-		obs_source_draw_async_texture(source);
+
+		if (nonlinear_alpha) {
+			gs_blend_state_push();
+			gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
+		}
+
+		obs_source_draw_texture(source, effect);
+
+		if (nonlinear_alpha) {
+			gs_blend_state_pop();
+		}
+
 		if (rotation) {
 			gs_matrix_pop();
 		}
@@ -2509,6 +2526,7 @@ static void source_render(obs_source_t *source, gs_effect_t *effect)
 	enum gs_color_format format = gs_get_format_from_space(source_space);
 	switch (source_space) {
 	case GS_CS_SRGB:
+	case GS_CS_SRGB_16F:
 		switch (current_space) {
 		case GS_CS_709_EXTENDED:
 			convert_tech = "Draw";
@@ -2521,6 +2539,7 @@ static void source_render(obs_source_t *source, gs_effect_t *effect)
 	case GS_CS_709_EXTENDED:
 		switch (current_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			convert_tech = "DrawTonemap";
 			break;
 		case GS_CS_709_SCRGB:
@@ -2531,6 +2550,7 @@ static void source_render(obs_source_t *source, gs_effect_t *effect)
 	case GS_CS_709_SCRGB:
 		switch (current_space) {
 		case GS_CS_SRGB:
+		case GS_CS_SRGB_16F:
 			convert_tech = "DrawMultiplyTonemap";
 			multiplier = 80.0f / obs_get_video_sdr_white_level();
 			break;
@@ -2796,7 +2816,9 @@ obs_source_get_color_space(obs_source_t *source, size_t count,
 	}
 
 	if (source->info.output_flags & OBS_SOURCE_ASYNC) {
-		return convert_video_space(source->async_format, count,
+		return convert_video_space(source->async_format,
+					   source->async_trc,
+					   source->async_color_format, count,
 					   preferred_spaces);
 	}
 

+ 36 - 14
libobs/obs.c

@@ -80,28 +80,35 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
 		video->conversion_needed = true;
 		video->conversion_width_i = 1.f / (float)ovi->output_width;
 		video->conversion_height_i = 1.f / (float)ovi->output_height;
-		if (ovi->colorspace == VIDEO_CS_2020_HLG) {
+		if (ovi->colorspace == VIDEO_CS_2020_PQ) {
+			video->conversion_techs[0] = "I010_PQ_Y";
+			video->conversion_techs[1] = "I010_PQ_U";
+			video->conversion_techs[2] = "I010_PQ_V";
+		} else if (ovi->colorspace == VIDEO_CS_2020_HLG) {
 			video->conversion_techs[0] = "I010_HLG_Y";
 			video->conversion_techs[1] = "I010_HLG_U";
 			video->conversion_techs[2] = "I010_HLG_V";
 			video->maximum_nits = 1000.f;
 		} else {
-			video->conversion_techs[0] = "I010_PQ_Y";
-			video->conversion_techs[1] = "I010_PQ_U";
-			video->conversion_techs[2] = "I010_PQ_V";
+			video->conversion_techs[0] = "I010_SRGB_Y";
+			video->conversion_techs[1] = "I010_SRGB_U";
+			video->conversion_techs[2] = "I010_SRGB_V";
 		}
 		break;
 	case VIDEO_FORMAT_P010:
 		video->conversion_needed = true;
 		video->conversion_width_i = 1.f / (float)ovi->output_width;
 		video->conversion_height_i = 1.f / (float)ovi->output_height;
-		if (ovi->colorspace == VIDEO_CS_2020_HLG) {
+		if (ovi->colorspace == VIDEO_CS_2020_PQ) {
+			video->conversion_techs[0] = "P010_PQ_Y";
+			video->conversion_techs[1] = "P010_PQ_UV";
+		} else if (ovi->colorspace == VIDEO_CS_2020_HLG) {
 			video->conversion_techs[0] = "P010_HLG_Y";
 			video->conversion_techs[1] = "P010_HLG_UV";
 			video->maximum_nits = 1000.f;
 		} else {
-			video->conversion_techs[0] = "P010_PQ_Y";
-			video->conversion_techs[1] = "P010_PQ_UV";
+			video->conversion_techs[0] = "P010_SRGB_Y";
+			video->conversion_techs[1] = "P010_SRGB_UV";
 		}
 	}
 }
@@ -372,12 +379,24 @@ static bool obs_init_textures(struct obs_video_info *ovi)
 	}
 
 	enum gs_color_format format = GS_RGBA;
+	switch (ovi->output_format) {
+	case VIDEO_FORMAT_I010:
+	case VIDEO_FORMAT_P010:
+		format = GS_RGBA16F;
+	}
+
 	enum gs_color_space space = GS_CS_SRGB;
 	switch (ovi->colorspace) {
 	case VIDEO_CS_2020_PQ:
 	case VIDEO_CS_2020_HLG:
-		format = GS_RGBA16F;
 		space = GS_CS_709_EXTENDED;
+		break;
+	default:
+		switch (ovi->output_format) {
+		case VIDEO_FORMAT_I010:
+		case VIDEO_FORMAT_P010:
+			space = GS_CS_SRGB_16F;
+		}
 	}
 
 	video->render_texture = gs_texture_create(ovi->base_width,
@@ -1927,13 +1946,16 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c,
 	const enum gs_color_space source_space = video->render_space;
 	const enum gs_color_space current_space = gs_get_color_space();
 	const char *tech_name = "Draw";
-	float multiplier = 1.0f;
-	if ((current_space == GS_CS_SRGB) &&
-	    (source_space == GS_CS_709_EXTENDED)) {
-		tech_name = "DrawTonemap";
-	} else if (current_space == GS_CS_709_SCRGB) {
+	float multiplier = 1.f;
+	switch (current_space) {
+	case GS_CS_SRGB:
+	case GS_CS_SRGB_16F:
+		if (source_space == GS_CS_709_EXTENDED)
+			tech_name = "DrawTonemap";
+		break;
+	case GS_CS_709_SCRGB:
 		tech_name = "DrawMultiply";
-		multiplier = obs_get_video_sdr_white_level() / 80.0f;
+		multiplier = obs_get_video_sdr_white_level() / 80.f;
 	}
 
 	const bool previous = gs_framebuffer_srgb_enabled();