From a86710ec5be9c0eb6d74b6fab350e3e571bb662a Mon Sep 17 00:00:00 2001 From: James Park Date: Mon, 22 Apr 2019 23:38:26 -0700 Subject: [PATCH] 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". --- UI/data/locale/en-US.ini | 4 +- libobs/data/format_conversion.effect | 56 ++++++++++ libobs/obs-internal.h | 2 + libobs/obs-source.c | 141 ++++++++++++------------ plugins/decklink/data/locale/en-US.ini | 4 +- plugins/decklink/decklink-source.cpp | 22 ---- plugins/win-dshow/data/locale/en-US.ini | 4 +- 7 files changed, 133 insertions(+), 100 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 35b697314..1c9672ae9 100644 --- a/UI/data/locale/en-US.ini +++ b/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" diff --git a/libobs/data/format_conversion.effect b/libobs/data/format_conversion.effect index 6ffe6adb0..b3d274e40 100644 --- a/libobs/data/format_conversion.effect +++ b/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); + } +} diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 15ded9d1e..de14f5111 100644 --- a/libobs/obs-internal.h +++ b/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; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 409140423..e6a9225ac 100644 --- a/libobs/obs-source.c +++ b/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; diff --git a/plugins/decklink/data/locale/en-US.ini b/plugins/decklink/data/locale/en-US.ini index 599b09d29..a73f79d83 100644 --- a/plugins/decklink/data/locale/en-US.ini +++ b/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" diff --git a/plugins/decklink/decklink-source.cpp b/plugins/decklink/decklink-source.cpp index b7ed0babb..f781898a7 100644 --- a/plugins/decklink/decklink-source.cpp +++ b/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); diff --git a/plugins/win-dshow/data/locale/en-US.ini b/plugins/win-dshow/data/locale/en-US.ini index 622971a3a..7f297c1f3 100644 --- a/plugins/win-dshow/data/locale/en-US.ini +++ b/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"