From 840e2b3d431001da3e2df32e406201c82e4b2fcf Mon Sep 17 00:00:00 2001 From: jpark37 Date: Fri, 23 Apr 2021 23:01:35 -0700 Subject: [PATCH 1/4] libobs: Add DrawNonlinearAlpha technique This allows OBS to mimic other programs that incorrectly use alpha. Requires premultiplied blend state. --- libobs/data/default.effect | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/libobs/data/default.effect b/libobs/data/default.effect index ebc6a7c93..0d754e1fd 100644 --- a/libobs/data/default.effect +++ b/libobs/data/default.effect @@ -33,6 +33,35 @@ float4 PSDrawAlphaDivide(VertInOut vert_in) : TARGET return float4(rgba.rgb * multiplier, alpha); } +float srgb_linear_to_nonlinear_channel(float u) +{ + return (u <= 0.0031308) ? (12.92 * u) : ((1.055 * pow(u, 1.0 / 2.4)) - 0.055); +} + +float3 srgb_linear_to_nonlinear(float3 v) +{ + return float3(srgb_linear_to_nonlinear_channel(v.r), srgb_linear_to_nonlinear_channel(v.g), srgb_linear_to_nonlinear_channel(v.b)); +} + +float srgb_nonlinear_to_linear_channel(float u) +{ + return (u <= 0.04045) ? (u / 12.92) : pow((u + 0.055) / 1.055, 2.4); +} + +float3 srgb_nonlinear_to_linear(float3 v) +{ + return float3(srgb_nonlinear_to_linear_channel(v.r), srgb_nonlinear_to_linear_channel(v.g), srgb_nonlinear_to_linear_channel(v.b)); +} + +float4 PSDrawNonlinearAlpha(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); + return rgba; +} + technique Draw { pass @@ -50,3 +79,12 @@ technique DrawAlphaDivide pixel_shader = PSDrawAlphaDivide(vert_in); } } + +technique DrawNonlinearAlpha +{ + pass + { + vertex_shader = VSDefault(vert_in); + pixel_shader = PSDrawNonlinearAlpha(vert_in); + } +} From 50dfa566e64091a640c3fbf8071970faeebafe19 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Sat, 24 Apr 2021 17:32:38 -0700 Subject: [PATCH 2/4] libobs: Filter using premultiplied alpha Convert incoming straight alpha color to premultiplied at filter begin, and process premultiplied value at filter end. If direct rendering is allowed, we assume the input source outputs premultiiplied alpha. If not, that source will need to be updated. --- libobs/obs-source.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index f5f98fe5f..87d8b5cd9 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -3701,10 +3701,11 @@ bool obs_source_process_filter_begin(obs_source_t *filter, filter->filter_texrender = gs_texrender_create(format, GS_ZS_NONE); - gs_blend_state_push(); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - if (gs_texrender_begin(filter->filter_texrender, cx, cy)) { + gs_blend_state_push(); + gs_blend_function_separate(GS_BLEND_SRCALPHA, GS_BLEND_ZERO, + GS_BLEND_ONE, GS_BLEND_ZERO); + bool custom_draw = (parent_flags & OBS_SOURCE_CUSTOM_DRAW) != 0; bool async = (parent_flags & OBS_SOURCE_ASYNC) != 0; struct vec4 clear_color; @@ -3718,10 +3719,10 @@ bool obs_source_process_filter_begin(obs_source_t *filter, else obs_source_video_render(target); + gs_blend_state_pop(); + gs_texrender_end(filter->filter_texrender); } - - gs_blend_state_pop(); return true; } @@ -3748,6 +3749,9 @@ static void obs_source_process_filter_tech_end_internal( const char *tech = tech_name ? tech_name : "Draw"; + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + if (can_bypass(target, parent, parent_flags, filter->allow_direct)) { render_filter_bypass(target, effect, tech); } else { @@ -3757,6 +3761,8 @@ static void obs_source_process_filter_tech_end_internal( } } + gs_blend_state_pop(); + gs_set_linear_srgb(previous); } From aed52412d41e7aaf3c2af319bac7d8a080b440f2 Mon Sep 17 00:00:00 2001 From: jpark37 Date: Sat, 24 Apr 2021 17:33:09 -0700 Subject: [PATCH 3/4] libobs: Render async video with DrawNonlinearAlpha Match previous OBS alpha behavior. DrawNonlinearAlpha requires premultiplied alpha blend state. --- libobs/obs-source.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 87d8b5cd9..f5d9538a1 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -2056,17 +2056,30 @@ 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); - tech = gs_effect_get_technique(effect, "Draw"); + const bool linear = gs_get_linear_srgb(); + const char *tech_name = linear ? "DrawNonlinearAlpha" : "Draw"; + premultiplied = linear; + 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); From b911e9b0ccfa62914bf0ab689c1d84498ed2640d Mon Sep 17 00:00:00 2001 From: jpark37 Date: Fri, 23 Apr 2021 23:03:27 -0700 Subject: [PATCH 4/4] image-source: Use DrawNonlinearAlpha Match previous OBS alpha behavior. DrawNonlinearAlpha requires premultiplied alpha blend state. --- plugins/image-source/image-source.c | 30 ++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/plugins/image-source/image-source.c b/plugins/image-source/image-source.c index 9f4c00867..a038f5df0 100644 --- a/plugins/image-source/image-source.c +++ b/plugins/image-source/image-source.c @@ -147,19 +147,31 @@ static void image_source_render(void *data, gs_effect_t *effect) if (!context->if2.image.texture) return; - const bool linear_srgb = gs_get_linear_srgb(); + effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + gs_technique_t *tech = + gs_effect_get_technique(effect, "DrawNonlinearAlpha"); const bool previous = gs_framebuffer_srgb_enabled(); - gs_enable_framebuffer_srgb(linear_srgb); + gs_enable_framebuffer_srgb(true); + + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); gs_eparam_t *const param = gs_effect_get_param_by_name(effect, "image"); - if (linear_srgb) - gs_effect_set_texture_srgb(param, context->if2.image.texture); - else - gs_effect_set_texture(param, context->if2.image.texture); + gs_effect_set_texture_srgb(param, context->if2.image.texture); - gs_draw_sprite(context->if2.image.texture, 0, context->if2.image.cx, - context->if2.image.cy); + size_t passes = gs_technique_begin(tech); + for (size_t i = 0; i < passes; i++) { + gs_technique_begin_pass(tech, i); + + gs_draw_sprite(context->if2.image.texture, 0, + context->if2.image.cx, context->if2.image.cy); + + gs_technique_end_pass(tech); + } + gs_technique_end(tech); + + gs_blend_state_pop(); gs_enable_framebuffer_srgb(previous); } @@ -298,7 +310,7 @@ static obs_missing_files_t *image_source_missingfiles(void *data) static struct obs_source_info image_source_info = { .id = "image_source", .type = OBS_SOURCE_TYPE_INPUT, - .output_flags = OBS_SOURCE_VIDEO, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW, .get_name = image_source_get_name, .create = image_source_create, .destroy = image_source_destroy,