Browse Source

libobs: Support limited color range for RGB/Y800 sources

libobs: Add support for limited to full color range conversions when
using RGB or Y800 formats, and move RGB converison for Y800 formats to
the GPU.

decklink: Stop hiding color space/range properties for RGB formats, and
remove "YUV" from "YUV Color Space" and "YUV Color Range".

win-dshow: Remove "YUV" from "YUV Color Space" and "YUV Color Range".

UI: Remove "YUV" from "YUV Color Space" and "YUV Color Range".
James Park 6 years ago
parent
commit
a86710ec5b

+ 2 - 2
UI/data/locale/en-US.ini

@@ -802,8 +802,8 @@ Basic.Settings.Advanced.General.ProcessPriority.Idle="Idle"
 Basic.Settings.Advanced.FormatWarning="Warning: Color formats other than NV12 are primarily intended for recording, and are not recommended when streaming. Streaming may incur increased CPU usage due to color format conversion."
 Basic.Settings.Advanced.Audio.BufferingTime="Audio Buffering Time"
 Basic.Settings.Advanced.Video.ColorFormat="Color Format"
-Basic.Settings.Advanced.Video.ColorSpace="YUV Color Space"
-Basic.Settings.Advanced.Video.ColorRange="YUV Color Range"
+Basic.Settings.Advanced.Video.ColorSpace="Color Space"
+Basic.Settings.Advanced.Video.ColorRange="Color Range"
 Basic.Settings.Advanced.Video.ColorRange.Partial="Partial"
 Basic.Settings.Advanced.Video.ColorRange.Full="Full"
 Basic.Settings.Advanced.Audio.MonitoringDevice="Monitoring Device"

+ 56 - 0
libobs/data/format_conversion.effect

@@ -349,6 +349,35 @@ float4 PSNV12_Reverse(VertInOut vert_in) : TARGET
 	return saturate(mul(float4(yuv, 1.0), color_matrix));
 }
 
+float4 PSY800_Limited(VertInOut vert_in) : TARGET
+{
+	int x = int(vert_in.uv.x * width  + PRECISION_OFFSET);
+	int y = int(vert_in.uv.y * height + PRECISION_OFFSET);
+
+	float limited = image.Load(int3(x, y, 0)).x;
+	float full = saturate((limited - (16.0 / 255.0)) * (255.0 / 219.0));
+	return float4(full, full, full, 1.0);
+}
+
+float4 PSY800_Full(VertInOut vert_in) : TARGET
+{
+	int x = int(vert_in.uv.x * width  + PRECISION_OFFSET);
+	int y = int(vert_in.uv.y * height + PRECISION_OFFSET);
+
+	float3 full = image.Load(int3(x, y, 0)).xxx;
+	return float4(full, 1.0);
+}
+
+float4 PSRGB_Limited(VertInOut vert_in) : TARGET
+{
+	int x = int(vert_in.uv.x * width  + PRECISION_OFFSET);
+	int y = int(vert_in.uv.y * height + PRECISION_OFFSET);
+
+	float4 rgba = image.Load(int3(x, y, 0));
+	rgba.rgb = saturate((rgba.rgb - (16.0 / 255.0)) * (255.0 / 219.0));
+	return rgba;
+}
+
 technique Planar420
 {
 	pass
@@ -447,3 +476,30 @@ technique NV12_Reverse
 		pixel_shader  = PSNV12_Reverse(vert_in);
 	}
 }
+
+technique Y800_Limited
+{
+	pass
+	{
+		vertex_shader = VSDefault(vert_in);
+		pixel_shader  = PSY800_Limited(vert_in);
+	}
+}
+
+technique Y800_Full
+{
+	pass
+	{
+		vertex_shader = VSDefault(vert_in);
+		pixel_shader  = PSY800_Full(vert_in);
+	}
+}
+
+technique RGB_Limited
+{
+	pass
+	{
+		vertex_shader = VSDefault(vert_in);
+		pixel_shader  = PSRGB_Limited(vert_in);
+	}
+}

+ 2 - 0
libobs/obs-internal.h

@@ -643,7 +643,9 @@ struct obs_source {
 	struct obs_source_frame         *cur_async_frame;
 	bool                            async_gpu_conversion;
 	enum video_format               async_format;
+	bool                            async_full_range;
 	enum video_format               async_cache_format;
+	bool                            async_cache_full_range;
 	enum gs_color_format            async_texture_format;
 	int                             async_plane_offset[2];
 	bool                            async_flip;

+ 69 - 72
libobs/obs-source.c

@@ -1330,9 +1330,12 @@ enum convert_type {
 	CONVERT_422_U,
 	CONVERT_422_Y,
 	CONVERT_444,
+	CONVERT_800,
+	CONVERT_RGB_LIMITED,
 };
 
-static inline enum convert_type get_convert_type(enum video_format format)
+static inline enum convert_type get_convert_type(enum video_format format,
+		bool full_range)
 {
 	switch (format) {
 	case VIDEO_FORMAT_I420:
@@ -1349,11 +1352,13 @@ static inline enum convert_type get_convert_type(enum video_format format)
 		return CONVERT_422_U;
 
 	case VIDEO_FORMAT_Y800:
+		return CONVERT_800;
+
 	case VIDEO_FORMAT_NONE:
 	case VIDEO_FORMAT_RGBA:
 	case VIDEO_FORMAT_BGRA:
 	case VIDEO_FORMAT_BGRX:
-		return CONVERT_NONE;
+		return full_range ? CONVERT_NONE : CONVERT_RGB_LIMITED;
 	}
 
 	return CONVERT_NONE;
@@ -1406,10 +1411,28 @@ static inline bool set_nv12_sizes(struct obs_source *source,
 	return true;
 }
 
+static inline bool set_y800_sizes(struct obs_source *source,
+	const struct obs_source_frame *frame)
+{
+	source->async_convert_width   = frame->width;
+	source->async_convert_height  = frame->height;
+	source->async_texture_format  = GS_R8;
+	return true;
+}
+
+static inline bool set_rgb_limited_sizes(struct obs_source *source,
+	const struct obs_source_frame *frame)
+{
+	source->async_convert_width   = frame->width;
+	source->async_convert_height  = frame->height;
+	source->async_texture_format  = convert_video_format(frame->format);
+	return true;
+}
+
 static inline bool init_gpu_conversion(struct obs_source *source,
 		const struct obs_source_frame *frame)
 {
-	switch (get_convert_type(frame->format)) {
+	switch (get_convert_type(frame->format, frame->full_range)) {
 		case CONVERT_422_Y:
 		case CONVERT_422_U:
 			return set_packed422_sizes(source, frame);
@@ -1423,6 +1446,12 @@ static inline bool init_gpu_conversion(struct obs_source *source,
 		case CONVERT_444:
 			return set_planar444_sizes(source, frame);
 
+		case CONVERT_800:
+			return set_y800_sizes(source, frame);
+
+		case CONVERT_RGB_LIMITED:
+			return set_rgb_limited_sizes(source, frame);
+
 		case CONVERT_NONE:
 			assert(false && "No conversion requested");
 			break;
@@ -1434,16 +1463,19 @@ static inline bool init_gpu_conversion(struct obs_source *source,
 bool set_async_texture_size(struct obs_source *source,
 		const struct obs_source_frame *frame)
 {
-	enum convert_type cur = get_convert_type(frame->format);
+	enum convert_type cur = get_convert_type(frame->format,
+			frame->full_range);
 
-	if (source->async_width  == frame->width  &&
-	    source->async_height == frame->height &&
-	    source->async_format == frame->format)
+	if (source->async_width      == frame->width  &&
+	    source->async_height     == frame->height &&
+	    source->async_format     == frame->format &&
+	    source->async_full_range == frame->full_range)
 		return true;
 
-	source->async_width  = frame->width;
-	source->async_height = frame->height;
-	source->async_format = frame->format;
+	source->async_width      = frame->width;
+	source->async_height     = frame->height;
+	source->async_format     = frame->format;
+	source->async_full_range = frame->full_range;
 
 	gs_enter_context(obs->video.graphics);
 
@@ -1459,8 +1491,10 @@ bool set_async_texture_size(struct obs_source *source,
 	if (cur != CONVERT_NONE && init_gpu_conversion(source, frame)) {
 		source->async_gpu_conversion = true;
 
+		enum gs_color_format format = CONVERT_RGB_LIMITED ?
+			convert_video_format(frame->format) : GS_BGRX;
 		source->async_texrender =
-			gs_texrender_create(GS_BGRX, GS_ZS_NONE);
+			gs_texrender_create(format, GS_ZS_NONE);
 
 		source->async_texture = gs_texture_create(
 				source->async_convert_width,
@@ -1489,23 +1523,17 @@ bool set_async_texture_size(struct obs_source *source,
 static void upload_raw_frame(gs_texture_t *tex,
 		const struct obs_source_frame *frame)
 {
-	switch (get_convert_type(frame->format)) {
+	switch (get_convert_type(frame->format, frame->full_range)) {
 		case CONVERT_422_U:
 		case CONVERT_422_Y:
+		case CONVERT_800:
+		case CONVERT_RGB_LIMITED:
 			gs_texture_set_image(tex, frame->data[0],
 					frame->linesize[0], false);
 			break;
 
 		case CONVERT_420:
-			gs_texture_set_image(tex, frame->data[0],
-					frame->width, false);
-			break;
-
 		case CONVERT_NV12:
-			gs_texture_set_image(tex, frame->data[0],
-					frame->width, false);
-			break;
-
 		case CONVERT_444:
 			gs_texture_set_image(tex, frame->data[0],
 					frame->width, false);
@@ -1517,7 +1545,8 @@ static void upload_raw_frame(gs_texture_t *tex,
 	}
 }
 
-static const char *select_conversion_technique(enum video_format format)
+static const char *select_conversion_technique(enum video_format format,
+		bool full_range)
 {
 	switch (format) {
 		case VIDEO_FORMAT_UYVY:
@@ -1539,11 +1568,16 @@ static const char *select_conversion_technique(enum video_format format)
 			return "I444_Reverse";
 
 		case VIDEO_FORMAT_Y800:
+			return full_range ? "Y800_Full" : "Y800_Limited";
+
 		case VIDEO_FORMAT_BGRA:
 		case VIDEO_FORMAT_BGRX:
 		case VIDEO_FORMAT_RGBA:
 		case VIDEO_FORMAT_NONE:
-			assert(false && "No conversion requested");
+			if (full_range)
+				assert(false && "No conversion requested");
+			else
+				return "RGB_Limited";
 			break;
 	}
 	return NULL;
@@ -1577,8 +1611,9 @@ static bool update_async_texrender(struct obs_source *source,
 	float convert_width  = (float)source->async_convert_width;
 
 	gs_effect_t *conv = obs->video.conversion_effect;
-	gs_technique_t *tech = gs_effect_get_technique(conv,
-			select_conversion_technique(frame->format));
+	const char *tech_name = select_conversion_technique(frame->format,
+			frame->full_range);
+	gs_technique_t *tech = gs_effect_get_technique(conv, tech_name);
 
 	if (!gs_texrender_begin(texrender, cx, cy)) {
 		GS_DEBUG_MARKER_END();
@@ -1632,7 +1667,7 @@ bool update_async_texture(struct obs_source *source,
 		const struct obs_source_frame *frame,
 		gs_texture_t *tex, gs_texrender_t *texrender)
 {
-	enum convert_type type      = get_convert_type(frame->format);
+	enum convert_type type;
 	uint8_t           *ptr;
 	uint32_t          linesize;
 
@@ -1641,6 +1676,7 @@ bool update_async_texture(struct obs_source *source,
 	if (source->async_gpu_conversion && texrender)
 		return update_async_texrender(source, frame, tex, texrender);
 
+	type = get_convert_type(frame->format, frame->full_range);
 	if (type == CONVERT_NONE) {
 		gs_texture_set_image(tex, frame->data[0], frame->linesize[0],
 				false);
@@ -2247,41 +2283,6 @@ static inline void copy_frame_data_plane(struct obs_source_frame *dst,
 				dst->linesize[plane] * lines);
 }
 
-static void copy_frame_data_line_y800(uint32_t *dst, uint8_t *src, uint8_t *end)
-{
-	while (src < end) {
-		register uint32_t val = *(src++);
-		val |= (val << 8);
-		val |= (val << 16);
-		*(dst++) = val;
-	}
-}
-
-static inline void copy_frame_data_y800(struct obs_source_frame *dst,
-		const struct obs_source_frame *src)
-{
-	uint32_t *ptr_dst;
-	uint8_t  *ptr_src;
-	uint8_t  *src_end;
-
-	if ((src->linesize[0] * 4) != dst->linesize[0]) {
-		for (uint32_t cy = 0; cy < src->height; cy++) {
-			ptr_dst = (uint32_t*)
-				(dst->data[0] + cy * dst->linesize[0]);
-			ptr_src = (src->data[0] + cy * src->linesize[0]);
-			src_end = ptr_src + src->width;
-
-			copy_frame_data_line_y800(ptr_dst, ptr_src, src_end);
-		}
-	} else {
-		ptr_dst = (uint32_t*)dst->data[0];
-		ptr_src = (uint8_t *)src->data[0];
-		src_end = ptr_src + src->height * src->linesize[0];
-
-		copy_frame_data_line_y800(ptr_dst, ptr_src, src_end);
-	}
-}
-
 static void copy_frame_data(struct obs_source_frame *dst,
 		const struct obs_source_frame *src)
 {
@@ -2320,11 +2321,8 @@ static void copy_frame_data(struct obs_source_frame *dst,
 	case VIDEO_FORMAT_RGBA:
 	case VIDEO_FORMAT_BGRA:
 	case VIDEO_FORMAT_BGRX:
-		copy_frame_data_plane(dst, src, 0, dst->height);
-		break;
-
 	case VIDEO_FORMAT_Y800:
-		copy_frame_data_y800(dst, src);
+		copy_frame_data_plane(dst, src, 0, dst->height);
 		break;
 	}
 }
@@ -2339,8 +2337,9 @@ static inline bool async_texture_changed(struct obs_source *source,
 		const struct obs_source_frame *frame)
 {
 	enum convert_type prev, cur;
-	prev = get_convert_type(source->async_cache_format);
-	cur  = get_convert_type(frame->format);
+	prev = get_convert_type(source->async_cache_format,
+			source->async_cache_full_range);
+	cur  = get_convert_type(frame->format, frame->full_range);
 
 	return source->async_cache_width  != frame->width ||
 	       source->async_cache_height != frame->height ||
@@ -2393,9 +2392,10 @@ static inline struct obs_source_frame *cache_video(struct obs_source *source,
 
 	if (async_texture_changed(source, frame)) {
 		free_async_cache(source);
-		source->async_cache_width  = frame->width;
-		source->async_cache_height = frame->height;
-		source->async_cache_format = frame->format;
+		source->async_cache_width      = frame->width;
+		source->async_cache_height     = frame->height;
+		source->async_cache_format     = frame->format;
+		source->async_cache_full_range = frame->full_range;
 	}
 
 	for (size_t i = 0; i < source->async_cache.num; i++) {
@@ -2414,9 +2414,6 @@ static inline struct obs_source_frame *cache_video(struct obs_source *source,
 		struct async_frame new_af;
 		enum video_format format = frame->format;
 
-		if (format == VIDEO_FORMAT_Y800)
-			format = VIDEO_FORMAT_BGRX;
-
 		new_frame = obs_source_frame_create(format,
 				frame->width, frame->height);
 		new_af.frame = new_frame;

+ 2 - 2
plugins/decklink/data/locale/en-US.ini

@@ -3,9 +3,9 @@ Device="Device"
 Mode="Mode"
 Buffering="Use Buffering"
 PixelFormat="Pixel Format"
-ColorSpace="YUV Color Space"
+ColorSpace="Color Space"
 ColorSpace.Default="Default"
-ColorRange="YUV Color Range"
+ColorRange="Color Range"
 ColorRange.Default="Default"
 ColorRange.Partial="Partial"
 ColorRange.Full="Full"

+ 0 - 22
plugins/decklink/decklink-source.cpp

@@ -220,9 +220,6 @@ static bool decklink_device_changed(obs_properties_t *props,
 	return true;
 }
 
-static bool color_format_changed(obs_properties_t *props,
-		obs_property_t *list, obs_data_t *settings);
-
 static bool mode_id_changed(obs_properties_t *props,
 		obs_property_t *list, obs_data_t *settings)
 {
@@ -231,24 +228,6 @@ static bool mode_id_changed(obs_properties_t *props,
 	list = obs_properties_get(props, PIXEL_FORMAT);
 	obs_property_set_visible(list, id != MODE_ID_AUTO);
 
-	return color_format_changed(props, nullptr, settings);
-}
-
-static bool color_format_changed(obs_properties_t *props,
-		obs_property_t *list, obs_data_t *settings)
-{
-	long long id = obs_data_get_int(settings, MODE_ID);
-	BMDPixelFormat pixelFormat = (BMDPixelFormat)obs_data_get_int(settings,
-		PIXEL_FORMAT);
-
-	list = obs_properties_get(props, COLOR_SPACE);
-	obs_property_set_visible(list,
-			id != MODE_ID_AUTO && pixelFormat == bmdFormat8BitYUV);
-
-	list = obs_properties_get(props, COLOR_RANGE);
-	obs_property_set_visible(list,
-			id == MODE_ID_AUTO || pixelFormat == bmdFormat8BitYUV);
-
 	return true;
 }
 
@@ -274,7 +253,6 @@ static obs_properties_t *decklink_get_properties(void *data)
 	list = obs_properties_add_list(props, PIXEL_FORMAT,
 			TEXT_PIXEL_FORMAT, OBS_COMBO_TYPE_LIST,
 			OBS_COMBO_FORMAT_INT);
-	obs_property_set_modified_callback(list, color_format_changed);
 
 	obs_property_list_add_int(list, "8-bit YUV", bmdFormat8BitYUV);
 	obs_property_list_add_int(list, "8-bit BGRA", bmdFormat8BitBGRA);

+ 2 - 2
plugins/win-dshow/data/locale/en-US.ini

@@ -1,9 +1,9 @@
 # video capture device text
 VideoCaptureDevice="Video Capture Device"
 Device="Device"
-ColorSpace="YUV Color Space"
+ColorSpace="Color Space"
 ColorSpace.Default="Default"
-ColorRange="YUV Color Range"
+ColorRange="Color Range"
 ColorRange.Partial="Partial"
 ColorRange.Full="Full"
 ConfigureAudio="Configure Audio"