From ad97eeebe7f5d946d2fb021d6983abceecbd6df9 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sun, 23 Dec 2018 18:27:24 +0100 Subject: [PATCH] filter-blur: Fix translucent source rendering and modernize code Translucent sources previously rendered with wrong alpha, which caused sources to look different than they should. With the new code, this is no longer the case and instead now renders the source correctly. Additionally the modernized code should reduce any problems with rendering by reducing the amount of duplicated manually written code and instead using classes. --- source/filter-blur.cpp | 449 ++++++++++++++++++++++------------------- source/filter-blur.h | 16 +- 2 files changed, 250 insertions(+), 215 deletions(-) diff --git a/source/filter-blur.cpp b/source/filter-blur.cpp index a88918b6..5e0ef8b7 100644 --- a/source/filter-blur.cpp +++ b/source/filter-blur.cpp @@ -237,49 +237,39 @@ bool filter::blur::blur_instance::modified_properties(void* ptr, obs_properties_ return true; } +bool filter::blur::blur_instance::can_log() +{ + // Only allow logging errors every 200ms. + auto now = std::chrono::high_resolution_clock::now(); + auto delta = (now - this->last_log); + std::swap(this->last_log, now); + return std::chrono::duration_cast(delta) > std::chrono::milliseconds(200); +} + filter::blur::blur_instance::blur_instance(obs_data_t* settings, obs_source_t* parent) { m_source = parent; - obs_enter_graphics(); - blur_effect = filter::blur::blur_factory::get()->get_effect(filter::blur::type::Box); - primary_rendertarget = gs_texrender_create(GS_RGBA, GS_ZS_NONE); - secondary_rendertarget = gs_texrender_create(GS_RGBA, GS_ZS_NONE); - horizontal_rendertarget = gs_texrender_create(GS_RGBA, GS_ZS_NONE); - vertical_rendertarget = gs_texrender_create(GS_RGBA, GS_ZS_NONE); - obs_leave_graphics(); - - if (!primary_rendertarget) { - P_LOG_ERROR(" Instance '%s' failed to create primary rendertarget.", - obs_source_get_name(m_source)); + // Create RenderTargets + try { + this->rt_source = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->rt_primary = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->rt_secondary = std::make_shared(GS_RGBA, GS_ZS_NONE); + } catch (std::exception ex) { + P_LOG_ERROR(" Failed to create rendertargets, error %s.", obs_source_get_name(m_source), + ex.what()); } - if (!secondary_rendertarget) { - P_LOG_ERROR(" Instance '%s' failed to create secondary rendertarget.", - obs_source_get_name(m_source)); - } - - if (!horizontal_rendertarget) { - P_LOG_ERROR(" Instance '%s' failed to create horizontal rendertarget.", - obs_source_get_name(m_source)); - } - - if (!vertical_rendertarget) { - P_LOG_ERROR(" Instance '%s' failed to create vertical rendertarget.", - obs_source_get_name(m_source)); - } + // Get initial Blur effect. + blur_effect = filter::blur::blur_factory::get()->get_effect(filter::blur::type::Box); update(settings); } filter::blur::blur_instance::~blur_instance() { - obs_enter_graphics(); - gs_texrender_destroy(primary_rendertarget); - gs_texrender_destroy(secondary_rendertarget); - gs_texrender_destroy(horizontal_rendertarget); - gs_texrender_destroy(vertical_rendertarget); - obs_leave_graphics(); + this->rt_primary.reset(); + this->rt_secondary.reset(); } obs_properties_t* filter::blur::blur_instance::get_properties() @@ -480,8 +470,8 @@ void filter::blur::blur_instance::video_tick(float) void filter::blur::blur_instance::video_render(gs_effect_t* effect) { - obs_source_t* parent = obs_filter_get_parent(m_source); - obs_source_t* target = obs_filter_get_target(m_source); + obs_source_t* parent = obs_filter_get_parent(this->m_source); + obs_source_t* target = obs_filter_get_target(this->m_source); uint32_t baseW = obs_source_get_base_width(target); uint32_t baseH = obs_source_get_base_height(target); vec4 black = {0}; @@ -490,185 +480,214 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) std::shared_ptr colorConversionEffect = blur_factory::get()->get_color_converter_effect(); - // Skip rendering if our target, parent or context is not valid. - if (!target || !parent || !m_source) { - obs_source_skip_video_filter(m_source); + // Verify that we can actually run first. + if (!target || !parent || !this->m_source) { + if (this->can_log()) { + P_LOG_ERROR(" Invalid context, memory corruption may have happened.", + obs_source_get_name(this->m_source)); + } + obs_source_skip_video_filter(this->m_source); return; } if ((baseW == 0) || (baseH == 0)) { - if (!have_logged_error) - P_LOG_ERROR(" Instance '%s' has invalid size source '%s'.", obs_source_get_name(m_source), - obs_source_get_name(target)); - have_logged_error = true; - obs_source_skip_video_filter(m_source); - return; - } - if (!primary_rendertarget || !blur_effect) { - if (!have_logged_error) - P_LOG_ERROR(" Instance '%s' is unable to render.", obs_source_get_name(m_source), - obs_source_get_name(target)); - have_logged_error = true; - obs_source_skip_video_filter(m_source); - return; - } - have_logged_error = false; - - gs_effect_t* defaultEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); - gs_texture_t* sourceTexture = nullptr; - -#pragma region Source To Texture - gs_texrender_reset(primary_rendertarget); - if (!gs_texrender_begin(primary_rendertarget, baseW, baseH)) { - P_LOG_ERROR(" Failed to set up base texture."); - obs_source_skip_video_filter(m_source); - return; - } else { - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - - // Clear to Black - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); - - // Render - if (obs_source_process_filter_begin(m_source, GS_RGBA, OBS_NO_DIRECT_RENDERING)) { - obs_source_process_filter_end(m_source, effect ? effect : defaultEffect, baseW, baseH); - } else { - P_LOG_ERROR(" Unable to render source."); - failed = true; + if (this->can_log()) { + P_LOG_ERROR(" Invalid base size from '%s': %lux%lu.", obs_source_get_name(this->m_source), + obs_source_get_name(target), baseW, baseH); } - gs_texrender_end(primary_rendertarget); + obs_source_skip_video_filter(this->m_source); + return; } - - if (failed) { - obs_source_skip_video_filter(m_source); + if (!this->rt_primary || !this->rt_secondary || !this->blur_effect) { + if (this->can_log()) { + P_LOG_ERROR(" Missing RenderTarget or Effect.", obs_source_get_name(this->m_source)); + } + obs_source_skip_video_filter(this->m_source); return; } - sourceTexture = gs_texrender_get_texture(primary_rendertarget); - if (!sourceTexture) { - P_LOG_ERROR(" Failed to get source texture."); - obs_source_skip_video_filter(m_source); - return; - } -#pragma endregion Source To Texture + gs_effect_t* defaultEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + std::shared_ptr tex_source; + std::shared_ptr tex_intermediate; - // Conversion -#pragma region RGB->YUV - if ((color_format == ColorFormat::YUV) && colorConversionEffect) { - gs_texrender_reset(secondary_rendertarget); - if (!gs_texrender_begin(secondary_rendertarget, baseW, baseH)) { - P_LOG_ERROR(" Failed to set up base texture."); - obs_source_skip_video_filter(m_source); - return; - } else { - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); + // Source To Texture + { + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_color(true, true, true, true); + gs_enable_blending(true); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + gs_depth_function(GS_ALWAYS); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); - // Clear to Black + try { + auto op = this->rt_source->render(baseW, baseH); + + // Orthographic Camera and clear RenderTarget. + gs_ortho(0, (float)baseW, 0, (float)baseH, -1., 1.); gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); - // Set up camera stuff - gs_set_cull_mode(GS_NEITHER); - gs_reset_blend_state(); - gs_enable_blending(false); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_enable_color(true, true, true, true); + // Render + if (obs_source_process_filter_begin(this->m_source, GS_RGBA, OBS_NO_DIRECT_RENDERING)) { + obs_source_process_filter_end(this->m_source, defaultEffect, baseW, baseH); + } else { + throw std::exception("Failed to render source"); + } + } catch (std::exception ex) { + if (this->can_log()) { + P_LOG_ERROR(" Rendering parent source to texture failed with error: %s.", + obs_source_get_name(this->m_source), ex.what()); + } + gs_blend_state_pop(); + obs_source_skip_video_filter(this->m_source); + return; + } + gs_blend_state_pop(); + + if (!(tex_source = this->rt_source->get_texture())) { + if (this->can_log()) { + P_LOG_ERROR(" Failed to get source texture."); + } + obs_source_skip_video_filter(m_source); + return; + } + } + + // Color Conversion RGB-YUV + if ((color_format == ColorFormat::YUV) && colorConversionEffect) { + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_color(true, true, true, true); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + gs_depth_function(GS_ALWAYS); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); + + try { + auto op = this->rt_primary->render(baseW, baseH); + gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); if (colorConversionEffect->has_parameter("image")) { - colorConversionEffect->get_parameter("image").set_texture(sourceTexture); + colorConversionEffect->get_parameter("image").set_texture(tex_source->get_object()); } while (gs_effect_loop(colorConversionEffect->get_object(), "RGBToYUV")) { - gs_draw_sprite(sourceTexture, 0, baseW, baseH); + gs_draw_sprite(tex_source->get_object(), 0, baseW, baseH); } - gs_texrender_end(secondary_rendertarget); + } catch (std::exception ex) { + if (this->can_log()) { + P_LOG_ERROR(" RGB-YUV conversion failed with error: %s.", + obs_source_get_name(this->m_source), ex.what()); + } + gs_blend_state_pop(); + obs_source_skip_video_filter(m_source); + return; } + gs_blend_state_pop(); - if (failed) { + if (!(tex_source = this->rt_primary->get_texture())) { + if (this->can_log()) { + P_LOG_ERROR(" Failed to get color conversion texture.", + obs_source_get_name(this->m_source)); + } obs_source_skip_video_filter(m_source); return; } - sourceTexture = gs_texrender_get_texture(secondary_rendertarget); - if (!sourceTexture) { - P_LOG_ERROR(" Failed to get source texture."); - obs_source_skip_video_filter(m_source); + // Swap RTs for further rendering. + std::swap(this->rt_primary, this->rt_secondary); + } + + // Blur + { + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_color(true, true, true, true); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + gs_depth_function(GS_ALWAYS); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); + + std::pair kvs[] = {{1.0f / baseW, 0.0f}, {0.0f, 1.0f / baseH}}; + tex_intermediate = tex_source; // We need the original to work. + + try { + for (auto v : kvs) { + float xpel = std::get<0>(v); + float ypel = std::get<1>(v); + + { + auto op = this->rt_primary->render(baseW, baseH); + gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); + + apply_shared_param(tex_intermediate->get_object(), xpel, ypel); + apply_gaussian_param(uint8_t(this->size)); + apply_bilateral_param(); + + // Render + while (gs_effect_loop(this->blur_effect->get_object(), this->blur_technique.c_str())) { + gs_draw_sprite(tex_intermediate->get_object(), 0, baseW, baseH); + } + } + + if (!(tex_intermediate = this->rt_primary->get_texture())) { + throw("Failed to get blur texture."); + } + + // Swap RTs for further rendering. + std::swap(this->rt_primary, this->rt_secondary); + } + } catch (std::exception ex) { + if (this->can_log()) { + P_LOG_ERROR(" Blur failed with error: %s.", obs_source_get_name(this->m_source), + ex.what()); + } + gs_blend_state_pop(); + obs_source_skip_video_filter(this->m_source); return; } + gs_blend_state_pop(); } -#pragma endregion RGB->YUV -#pragma region Blur - // Set up camera stuff - gs_set_cull_mode(GS_NEITHER); - gs_reset_blend_state(); - gs_enable_blending(true); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_enable_color(true, true, true, true); - - gs_texture_t* blurred = nullptr; - gs_texture_t* intermediate = sourceTexture; - - std::tuple kvs[] = { - std::make_tuple("Horizontal", horizontal_rendertarget, 1.0f / baseW, 0.0f), - std::make_tuple("Vertical", vertical_rendertarget, 0.0f, 1.0f / baseH), - }; - - for (auto v : kvs) { - const char* name = std::get<0>(v); - gs_texrender_t* rt = std::get<1>(v); - float xpel = std::get<2>(v), ypel = std::get<3>(v); - - if (!apply_shared_param(intermediate, xpel, ypel)) - break; - apply_gaussian_param(this->size); - apply_bilateral_param(); - - gs_texrender_reset(rt); - if (!gs_texrender_begin(rt, baseW, baseH)) { - P_LOG_ERROR(" Failed to begin rendering.", name); - break; - } - - // Camera - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); - - // Render - while (gs_effect_loop(this->blur_effect->get_object(), this->blur_technique.c_str())) { - gs_draw_sprite(intermediate, 0, baseW, baseH); - } - - gs_texrender_end(rt); - intermediate = gs_texrender_get_texture(rt); - if (!intermediate) { - P_LOG_ERROR(" Failed to get intermediate texture.", name); - break; - } - blurred = intermediate; - } - if (blurred == nullptr) { - obs_source_skip_video_filter(m_source); - return; - } -#pragma endregion blur - -#pragma region Mask + // Mask if (mask.enabled) { + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_color(true, true, true, true); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + gs_depth_function(GS_ALWAYS); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); + std::string technique = ""; - switch (mask.type) { + switch (this->mask.type) { case Region: - if (mask.region.feather > 0.001) { - if (mask.region.invert) { + if (this->mask.region.feather > 0.001) { + if (this->mask.region.invert) { technique = "RegionFeatherInverted"; } else { technique = "RegionFeather"; } } else { - if (mask.region.invert) { + if (this->mask.region.invert) { technique = "RegionInverted"; } else { technique = "Region"; @@ -682,8 +701,8 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) } if (mask.source.source_texture) { - uint32_t source_width = obs_source_get_width(mask.source.source_texture->get_object()); - uint32_t source_height = obs_source_get_height(mask.source.source_texture->get_object()); + uint32_t source_width = obs_source_get_width(this->mask.source.source_texture->get_object()); + uint32_t source_height = obs_source_get_height(this->mask.source.source_texture->get_object()); if (source_width == 0) { source_width = baseW; @@ -691,7 +710,7 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) if (source_height == 0) { source_height = baseH; } - if (mask.source.is_scene) { + if (this->mask.source.is_scene) { obs_video_info ovi; if (obs_get_video_info(&ovi)) { source_width = ovi.base_width; @@ -699,35 +718,57 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) } } - mask.source.texture = mask.source.source_texture->render(source_width, source_height); + this->mask.source.texture = this->mask.source.source_texture->render(source_width, source_height); } std::shared_ptr mask_effect = blur_factory::get()->get_mask_effect(); - apply_mask_parameters(mask_effect, sourceTexture, blurred); + apply_mask_parameters(mask_effect, tex_source->get_object(), tex_intermediate->get_object()); - gs_texrender_reset(horizontal_rendertarget); - if (gs_texrender_begin(horizontal_rendertarget, baseW, baseH)) { - // Camera + try { + auto op = this->rt_primary->render(baseW, baseH); gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); // Render while (gs_effect_loop(mask_effect->get_object(), technique.c_str())) { - gs_draw_sprite(blurred, 0, baseW, baseH); + gs_draw_sprite(tex_intermediate->get_object(), 0, baseW, baseH); } - gs_texrender_end(horizontal_rendertarget); - - blurred = gs_texrender_get_texture(horizontal_rendertarget); + } catch (std::exception ex) { + if (this->can_log()) { + P_LOG_ERROR(" Masking failed with error: %s.", obs_source_get_name(this->m_source), + ex.what()); + } + gs_blend_state_pop(); + obs_source_skip_video_filter(this->m_source); + return; } + gs_blend_state_pop(); + + if (!(tex_intermediate = this->rt_primary->get_texture())) { + if (this->can_log()) { + P_LOG_ERROR(" Failed to get masked texture.", obs_source_get_name(this->m_source)); + } + obs_source_skip_video_filter(this->m_source); + return; + } + + // Swap RTs for further rendering. + std::swap(this->rt_primary, this->rt_secondary); } -#pragma endregion - -#pragma region YUV->RGB or straight draw - // Draw final effect + // Color Conversion RGB-YUV or Straight Draw { - gs_effect_t* finalEffect = defaultEffect; + // It is important that we do not modify the blend state here, as it is set correctly by OBS + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_set_cull_mode(GS_NEITHER); + gs_depth_function(GS_ALWAYS); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); + + gs_effect_t* finalEffect = effect ? effect : defaultEffect; const char* technique = "Draw"; if ((color_format == ColorFormat::YUV) && colorConversionEffect) { @@ -735,28 +776,17 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) technique = "YUVToRGB"; } - // Set up camera stuff - gs_set_cull_mode(GS_NEITHER); - gs_reset_blend_state(); - gs_enable_blending(true); - gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_enable_color(true, true, true, true); - gs_eparam_t* param = gs_effect_get_param_by_name(finalEffect, "image"); if (!param) { - P_LOG_ERROR(" Failed to set image param."); + P_LOG_ERROR(" Failed to set image param.", obs_source_get_name(this->m_source)); failed = true; } else { - gs_effect_set_texture(param, blurred); + gs_effect_set_texture(param, tex_intermediate->get_object()); } while (gs_effect_loop(finalEffect, technique)) { - gs_draw_sprite(blurred, 0, baseW, baseH); + gs_draw_sprite(tex_intermediate->get_object(), 0, baseW, baseH); } } -#pragma endregion YUV->RGB or straight draw if (failed) { obs_source_skip_video_filter(m_source); @@ -1010,6 +1040,7 @@ void filter::blur::blur_factory::scene_destroy_handler(void* ptr, calldata_t* da std::shared_ptr filter::blur::blur_factory::get_effect(filter::blur::type type) { + type; return blur_effect; } diff --git a/source/filter-blur.h b/source/filter-blur.h index 2abf10ba..f755c514 100644 --- a/source/filter-blur.h +++ b/source/filter-blur.h @@ -21,6 +21,7 @@ #define OBS_STREAM_EFFECTS_FILTER_BLUR_HPP #pragma once +#include #include #include #include @@ -29,6 +30,7 @@ #include "gs-effect.h" #include "gs-helper.h" #include "gs-texture.h" +#include "gs-rendertarget.h" #include "plugin.h" extern "C" { @@ -56,10 +58,9 @@ namespace filter { class blur_instance { obs_source_t* m_source; - gs_texrender_t* primary_rendertarget; - gs_texrender_t* secondary_rendertarget; - gs_texrender_t* horizontal_rendertarget; - gs_texrender_t* vertical_rendertarget; + std::shared_ptr rt_source; + std::shared_ptr rt_primary; + std::shared_ptr rt_secondary; // blur std::shared_ptr blur_effect; @@ -71,7 +72,7 @@ namespace filter { double_t bilateral_smoothing; double_t bilateral_sharpness; - // Regional + // Masking struct { bool enabled; mask_type type; @@ -106,7 +107,6 @@ namespace filter { } mask; // advanced - bool have_logged_error = false; uint64_t color_format; bool apply_shared_param(gs_texture_t* input, float texelX, float texelY); @@ -118,6 +118,10 @@ namespace filter { static bool modified_properties(void* ptr, obs_properties_t* props, obs_property* prop, obs_data_t* settings); + // Logging + std::chrono::high_resolution_clock::time_point last_log; + bool can_log(); + public: blur_instance(obs_data_t* settings, obs_source_t* self); ~blur_instance();