From 9595a78e8a272cc15ada4d456bc547ffcac3b73e Mon Sep 17 00:00:00 2001 From: jp9000 Date: Fri, 30 May 2014 02:23:36 -0700 Subject: [PATCH] libobs: Add planar 4:2:0 async texture support This uses the reverse planar YUV 4:2:0 conversion shader to output a YUV texture without having to convert it via CPU. Again, this will reduce video upload bandwidth usage to 37.5% of the original rate. I suspect this will be particularly useful for when an FFmpeg or libav input plugin for playing videos is made. NOTE: There's an issue with certain texture sizes right now I haven't been able to identify, if the full size of texture data divided by the base texture width is an uneven number, the V chroma plane seems like it can potentially shift, though I only had this happen with 160x90 resolution C920. Almost all resolutions tend to be even. Needs further testing with more devices that support planar YUV 4:2:0 output. --- libobs/obs-internal.h | 2 ++ libobs/obs-source.c | 74 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index eaf6d4faa..18769fa97 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -288,10 +288,12 @@ struct obs_source { texrender_t async_convert_texrender; bool async_gpu_conversion; enum video_format async_format; + enum gs_color_format async_texture_format; float async_color_matrix[16]; bool async_full_range; float async_color_range_min[3]; float async_color_range_max[3]; + int async_plane_offset[2]; bool async_flip; DARRAY(struct source_frame*) video_frames; pthread_mutex_t video_mutex; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index d6f3af7d4..84ed41890 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -608,7 +608,23 @@ static inline bool set_packed422_sizes(struct obs_source *source, struct source_frame *frame) { source->async_convert_height = frame->height; - source->async_convert_width = frame->width / 2; + source->async_convert_width = frame->width / 2; + source->async_texture_format = GS_BGRA; + return true; +} + +static inline bool set_planar420_sizes(struct obs_source *source, + struct source_frame *frame) +{ + uint32_t size = frame->width * frame->height; + size += size/2; + + source->async_convert_width = frame->width; + source->async_convert_height = (size / frame->width + 1) & 0xFFFFFFFE; + source->async_texture_format = GS_R8; + source->async_plane_offset[0] = frame->width * frame->height; + source->async_plane_offset[1] = source->async_plane_offset[0] + + frame->width * frame->height / 4; return true; } @@ -620,8 +636,11 @@ static inline bool init_gpu_conversion(struct obs_source *source, case CONVERT_422_U: return set_packed422_sizes(source, frame); - case CONVERT_NV12: case CONVERT_420: + return set_planar420_sizes(source, frame); + + case CONVERT_NV12: + assert(false && "NV12 not yet implemented"); /* TODO: implement conversion */ break; @@ -633,6 +652,17 @@ static inline bool init_gpu_conversion(struct obs_source *source, return false; } +static inline enum gs_color_format convert_video_format( + enum video_format format) +{ + if (format == VIDEO_FORMAT_RGBA) + return GS_RGBA; + else if (format == VIDEO_FORMAT_BGRA) + return GS_BGRA; + + return GS_BGRX; +} + static inline bool set_async_texture_size(struct obs_source *source, struct source_frame *frame) { @@ -654,19 +684,22 @@ static inline bool set_async_texture_size(struct obs_source *source, source->async_gpu_conversion = true; source->async_convert_texrender = - texrender_create(GS_RGBA, GS_ZS_NONE); + texrender_create(GS_BGRX, GS_ZS_NONE); source->async_texture = gs_create_texture( source->async_convert_width, source->async_convert_height, - GS_RGBA, 1, NULL, GS_DYNAMIC); + source->async_texture_format, + 1, NULL, GS_DYNAMIC); } else { + enum gs_color_format format = convert_video_format( + frame->format); source->async_gpu_conversion = false; source->async_texture = gs_create_texture( frame->width, frame->height, - GS_RGBA, 1, NULL, GS_DYNAMIC); + format, 1, NULL, GS_DYNAMIC); } if (!source->async_texture) @@ -686,8 +719,12 @@ static void upload_raw_frame(texture_t tex, const struct source_frame *frame) frame->linesize[0], false); break; - case CONVERT_NV12: case CONVERT_420: + texture_setimage(tex, frame->data[0], + frame->width, false); + break; + + case CONVERT_NV12: assert(false && "Conversion not yet implemented"); break; @@ -709,8 +746,10 @@ static const char *select_conversion_technique(enum video_format format) case VIDEO_FORMAT_YVYU: return "YVYU_Reverse"; - case VIDEO_FORMAT_NV12: case VIDEO_FORMAT_I420: + return "I420_Reverse"; + + case VIDEO_FORMAT_NV12: assert(false && "Conversion not yet implemented"); break; @@ -743,6 +782,9 @@ static bool update_async_texrender(struct obs_source *source, uint32_t cx = source->async_width; uint32_t cy = source->async_height; + float convert_width = (float)source->async_convert_width; + float convert_height = (float)source->async_convert_height; + effect_t conv = obs->video.conversion_effect; technique_t tech = effect_gettechnique(conv, select_conversion_technique(frame->format)); @@ -753,17 +795,25 @@ static bool update_async_texrender(struct obs_source *source, technique_begin(tech); technique_beginpass(tech, 0); - effect_settexture(conv, effect_getparambyname(conv, "image"), - tex); + effect_settexture(conv, effect_getparambyname(conv, "image"), tex); set_eparam(conv, "width", (float)cx); set_eparam(conv, "height", (float)cy); set_eparam(conv, "width_i", 1.0f / cx); set_eparam(conv, "height_i", 1.0f / cy); - set_eparam(conv, "width_d2", cx * 0.5f); + set_eparam(conv, "width_d2", cx * 0.5f); set_eparam(conv, "height_d2", cy * 0.5f); - set_eparam(conv, "width_d2_i", 1.0f / (cx * 0.5f)); + set_eparam(conv, "width_d2_i", 1.0f / (cx * 0.5f)); set_eparam(conv, "height_d2_i", 1.0f / (cy * 0.5f)); - set_eparam(conv, "input_height", (float)cy); + set_eparam(conv, "input_width", convert_width); + set_eparam(conv, "input_height", convert_height); + set_eparam(conv, "input_width_i", 1.0f / convert_width); + set_eparam(conv, "input_height_i", 1.0f / convert_height); + set_eparam(conv, "input_width_i_d2", (1.0f / convert_width) * 0.5f); + set_eparam(conv, "input_height_i_d2", (1.0f / convert_height) * 0.5f); + set_eparam(conv, "u_plane_offset", + (float)source->async_plane_offset[0]); + set_eparam(conv, "v_plane_offset", + (float)source->async_plane_offset[1]); gs_ortho(0.f, (float)cx, 0.f, (float)cy, -100.f, 100.f);