From 4dbf4142145dd3f390d4d8c60b4a1b08b4bf0f67 Mon Sep 17 00:00:00 2001 From: Michael Fabian Dirks Date: Sun, 20 Aug 2017 00:19:58 +0200 Subject: [PATCH] effects, filter-blur: Precalculated Kernel, Formatting, Rendering and more Filter: * Massively improved Rendering loop to reduce overhead and provide cleaner code. Gaussian Blur: * Added precalculated Kernels using Textures. Slightly reduced GPU load. * Added basic attempt at a Nvidia o(n/2+1) gaussian blur implementation. * Fix up LOD for texture sampling. Box Blur: * Halved loop count. --- data/effects/bilateral-blur.effect | 29 +- data/effects/box-blur.effect | 22 +- data/effects/gaussian-blur.effect | 103 ++++- source/filter-blur.cpp | 595 +++++++++++++++-------------- source/filter-blur.h | 30 +- 5 files changed, 450 insertions(+), 329 deletions(-) diff --git a/data/effects/bilateral-blur.effect b/data/effects/bilateral-blur.effect index eb758993..e31912fb 100644 --- a/data/effects/bilateral-blur.effect +++ b/data/effects/bilateral-blur.effect @@ -1,8 +1,15 @@ +// OBS Default uniform float4x4 ViewProj; -uniform texture2d image; -uniform float2 texel; -uniform int widthHalf; -uniform int width; + +// Settings (Shared) +uniform texture2d u_image; +uniform float2 u_imageSize; +uniform float2 u_imageTexelDelta; +uniform int u_radius; +uniform int u_diameter; +uniform float2 u_texelDelta; + +// Settings (Private) uniform float bilateralSmoothing; uniform float bilateralSharpness; @@ -43,8 +50,8 @@ float Bilateral3(float3 v, float sigma) float4 PSBilateral(VertDataOut v_in) : TARGET { - float4 source = image.Sample(textureSampler, v_in.uv); - + float4 source = image.Sample(textureSampler, v_in.uv); + float3 color = float3(0, 0, 0); float2 rootuv = v_in.uv - (texel * widthHalf); float Z = 0.0; @@ -52,20 +59,20 @@ float4 PSBilateral(VertDataOut v_in) : TARGET for (int k = -widthHalf; k <= widthHalf; k++) { // Sample Color float3 sample = image.Sample(textureSampler, rootuv); - + // Bilateral Stuff float bKernel = Bilateral(abs(k), bilateralSmoothing); - bKernel *= bKernel; + bKernel *= bKernel; float factor = Bilateral3(sample - source.rgb, bilateralSharpness) * bZ * bKernel; Z += factor; - + // Store Color color += factor * sample; - + // Advance UV rootuv += texel; } - + return float4(color.rgb / Z, source.a); } diff --git a/data/effects/box-blur.effect b/data/effects/box-blur.effect index a1d38965..1b6210c6 100644 --- a/data/effects/box-blur.effect +++ b/data/effects/box-blur.effect @@ -1,8 +1,13 @@ +// OBS Default uniform float4x4 ViewProj; -uniform texture2d image; -uniform float2 texel; -uniform int widthHalf; -uniform int width; + +// Settings (Shared) +uniform texture2d u_image; +uniform float2 u_imageSize; +uniform float2 u_imageTexelDelta; +uniform int u_radius; +uniform int u_diameter; +uniform float2 u_texelDelta; sampler_state textureSampler { Filter = Point; @@ -31,11 +36,12 @@ VertDataOut VSDefault(VertDataIn v_in) // Box Blur float4 PSBox(VertDataOut v_in) : TARGET { - float4 rgba = float4(0,0,0,0); - for (int k = -widthHalf; k <= widthHalf; k++) { - rgba += image.Sample(textureSampler, v_in.uv + (texel * k)); + float4 rgba = u_image.Sample(textureSampler, v_in.uv); + for (int k = 1; k <= u_radius; k++) { + rgba += u_image.Sample(textureSampler, v_in.uv + (u_texelDelta * k)); + rgba += u_image.Sample(textureSampler, v_in.uv - (u_texelDelta * k)); } - rgba = rgba / width; + rgba = rgba / u_diameter; return rgba; } diff --git a/data/effects/gaussian-blur.effect b/data/effects/gaussian-blur.effect index d8c0ac0c..eace1247 100644 --- a/data/effects/gaussian-blur.effect +++ b/data/effects/gaussian-blur.effect @@ -1,13 +1,32 @@ +// OBS Default uniform float4x4 ViewProj; -uniform texture2d image; -uniform float2 texel; -uniform int widthHalf; -uniform int width; -sampler_state textureSampler { +// Settings (Shared) +uniform texture2d u_image; +uniform float2 u_imageSize; +uniform float2 u_imageTexelDelta; +uniform int u_radius; +uniform int u_diameter; +uniform float2 u_texelDelta; + +// Settings (Private) +//uniform float registerkernel[25]; +uniform texture2d kernel; +uniform float2 kernelTexel; + +sampler_state pointClampSampler { Filter = Point; AddressU = Clamp; AddressV = Clamp; + MinLOD = 0; + MaxLOD = 0; +}; +sampler_state bilinearClampSampler { + Filter = Bilinear; + AddressU = Clamp; + AddressV = Clamp; + MinLOD = 0; + MaxLOD = 0; }; struct VertDataIn { @@ -34,19 +53,69 @@ float Gaussian(float x, float o) { return (1.0 / (o * sqrt(2.0 * pivalue))) * exp((-(x * x)) / (2 * (o * o))); } -float4 PSGaussian(VertDataOut v_in) : TARGET { - const float pivalue = 3.1415926535897932384626433832795; - - float4 rgba = image.Sample(textureSampler, v_in.uv) * Gaussian(0, width / pivalue); - float2 uvoffset = texel; - for (int k = 1; k <= widthHalf; k++) { - float g = Gaussian(k, width / pivalue); - float4 spos = image.Sample(textureSampler, v_in.uv + uvoffset) * g; - float4 sneg = image.Sample(textureSampler, v_in.uv - uvoffset) * g; - rgba += spos + sneg; - uvoffset += texel; +float4 InternalGaussian( + float2 p_uv, float2 p_uvStep, int p_size, + texture2d p_source) { + float l_gauss = Gaussian(0, p_size); + float4 l_value = p_source.Sample(pointClampSampler, p_uv) * gauss; + float2 l_uvoffset = p_uvStep; + for (int k = 1; k <= p_size; k++) { + float l_g = Gaussian(k, p_size); + float4 l_p = p_source.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g; + float4 l_n = p_source.Sample(pointClampSampler, p_uv - l_uvoffset) * l_g; + l_value += l_p + l_n; + l_uvoffset += p_uvStep; + l_gauss += l_g; } - return rgba; + l_value = l_value * (1.0 / l_gauss); + return l_value; +} + +float4 InternalGaussianPrecalculated( + float2 p_uv, float2 p_uvStep, int p_size, + texture2d p_source, float2 p_sourceStep, + texture2d p_kernel, float2 p_kernelStep) { + float4 l_value = p_source.Sample(pointClampSampler, p_uv + imageTexel) + * kernel.Sample(pointClampSampler, float2(0, 0)).r; + float2 l_uvoffset = p_uvStep; + float2 l_koffset = p_kerneltexel; + for (int k = 1; k <= p_size; k++) { + float l_g = p_kernel.Sample(pointClampSampler, l_koffset).r; + float4 l_p = p_source.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g; + float4 l_n = p_source.Sample(pointClampSampler, p_uv - l_uvoffset) * l_g; + l_value += l_p + l_n; + l_uvoffset += p_uvStep; + l_koffset += p_kerneltexel; + } + return l_value; +} + +float4 InternalGaussianPrecalculatedNVOptimized(float2 p_uv, texture2d p_source, float2 p_texel, + int p_size, texture2d p_kernel, texture2d p_kerneltexel) { + if (p_size % 2 == 0) { + float4 l_value = p_source.Sample(pointClampSampler, p_uv) + * kernel.Sample(pointClampSampler, float2(0, 0)).r; + float2 l_uvoffset = p_texel; + float2 l_koffset = p_kerneltexel; + for (int k = 1; k <= p_size; k++) { + float l_g = p_kernel.Sample(pointClampSampler, l_koffset).r; + float4 l_p = p_source.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g; + float4 l_n = p_source.Sample(pointClampSampler, p_uv - l_uvoffset) * l_g; + l_value += l_p + l_n; + l_uvoffset += p_texel; + l_koffset += p_kerneltexel; + } + return l_value; + } else { + return InternalGaussianPrecalculated(p_uv, p_source, p_texel, p_size, p_kernel, p_kerneltexel);) + } +} + +float4 PSGaussian(VertDataOut v_in) : TARGET { + //return InternalGaussian(v_in.uv, u_image, u_imageTexelDelta, u_radius); + //return InternalGaussianPrecalculated(v_in.uv, image, texel, radius, kernel, kernelTexel); + return InternalGaussianPrecalculatedNVOptimize(v_in.uv, u_ + image, texel, radius, kernel, kernelTexel); } technique Draw diff --git a/source/filter-blur.cpp b/source/filter-blur.cpp index fc12411c..90eb09f4 100644 --- a/source/filter-blur.cpp +++ b/source/filter-blur.cpp @@ -46,13 +46,64 @@ static gs_effect_t* g_boxBlurEffect, *g_colorConversionEffect; static size_t g_maxKernelSize = 25; +const double_t pi = 3.1415926535897932384626433832795; +const double_t twopi = 6.283185307179586476925286766559; +const double_t twopisqr = 2.506628274631000502415765284811; +const double_t half = 0.5; + double_t gaussian1D(double_t x, double_t o) { - return (1.0 / (o * sqrt(2 * M_PI))) * exp(-(x*x) / (2 * (o*o))); + double_t c = (x / o); + double_t b = exp(-half * c * c); + double_t a = (1.0 / (o * twopisqr)); + return a * b; } double_t bilateral(double_t x, double_t o) { return 0.39894 * exp(-0.5 * (x * x) / (o * o)) / o; } +static void makeGaussianKernels() { + g_gaussianBlur.kernels.resize(g_maxKernelSize); + + std::vector textureBuffer; + std::vector mathBuffer; + + for (size_t n = 1; n <= g_maxKernelSize; n++) { + textureBuffer.resize(n * 4); + mathBuffer.resize(n); + + // Calculate and normalize + double_t sum = 0.0; + for (size_t p = 0; p < n; p++) { + double_t g = gaussian1D(double_t(p), double_t(n)); + mathBuffer[p] = g; + sum += g; + if (p != 0) + sum += g; + } + for (size_t p = 0; p < n; p++) { + mathBuffer[p] /= sum; + } + + // Build Texture + for (size_t p = 0; p < n; p++) { + textureBuffer[p * 4] = + textureBuffer[p * 4 + 1] = + textureBuffer[p * 4 + 2] = + textureBuffer[p * 4 + 3] = (float_t)mathBuffer[p]; + } + + uint8_t* data = reinterpret_cast(textureBuffer.data()); + const uint8_t** pdata = const_cast(&data); + + gs_texture_t* tex = gs_texture_create(uint32_t(n), 1, gs_color_format::GS_RGBA32F, 1, pdata, 0); + if (!tex) { + P_LOG_ERROR(" Failed to create gaussian kernel for %d width.", n); + } else { + g_gaussianBlur.kernels[n - 1] = tex; + } + } +} + Filter::Blur::Blur() { memset(&sourceInfo, 0, sizeof(obs_source_info)); sourceInfo.id = "obs-stream-effects-filter-blur"; @@ -82,10 +133,10 @@ Filter::Blur::Blur() { g_boxBlurEffect = gs_effect_create_from_file(file, &loadError); bfree(file); if (loadError != nullptr) { - PLOG_ERROR(" Loading box-blur effect failed with error(s): %s", loadError); + P_LOG_ERROR(" Loading box-blur effect failed with error(s): %s", loadError); bfree(loadError); } else if (!g_boxBlurEffect) { - PLOG_ERROR(" Loading box-blur effect failed with unspecified error."); + P_LOG_ERROR(" Loading box-blur effect failed with unspecified error."); } } /// Gaussian Blur @@ -96,38 +147,13 @@ Filter::Blur::Blur() { effect = gs_effect_create_from_file(file, &loadError); bfree(file); if (loadError != nullptr) { - PLOG_ERROR(" Loading gaussian blur effect failed with error(s): %s", loadError); + P_LOG_ERROR(" Loading gaussian blur effect failed with error(s): %s", loadError); bfree(loadError); } else if (!effect) { - PLOG_ERROR(" Loading gaussian blur effect failed with unspecified error."); + P_LOG_ERROR(" Loading gaussian blur effect failed with unspecified error."); } else { g_gaussianBlur.effect = effect; - g_gaussianBlur.kernels.resize(g_maxKernelSize); - std::vector databuf; - for (size_t n = 1; n <= g_maxKernelSize; n++) { - databuf.resize(n); - // Calculate - double_t sum = 0.0; - for (size_t p = 0; p < n; p ++) { - databuf[p] = gaussian1D(p, n); - sum += databuf[p]; - if (p != 0) - sum += databuf[p]; - } - // Normalize - for (size_t p = 0; p < n; p++) { - databuf[p] /= sum; - } - uint8_t* data = reinterpret_cast(databuf.data()); - const uint8_t** pdata = const_cast(&data); - gs_texture_t* tex = gs_texture_create(n, 1, gs_color_format::GS_R32F, 1, pdata, 0); - if (!tex) { - PLOG_ERROR(" Failed to create gaussian kernel for %d width.", n); - } else { - g_gaussianBlur.kernels[n - 1] = tex; - } - } } } /// Bilateral Blur @@ -138,41 +164,16 @@ Filter::Blur::Blur() { effect = gs_effect_create_from_file(file, &loadError); bfree(file); if (loadError != nullptr) { - PLOG_ERROR(" Loading bilateral blur effect failed with error(s): %s", loadError); + P_LOG_ERROR(" Loading bilateral blur effect failed with error(s): %s", loadError); bfree(loadError); } else if (!effect) { - PLOG_ERROR(" Loading bilateral blur effect failed with unspecified error."); + P_LOG_ERROR(" Loading bilateral blur effect failed with unspecified error."); } else { g_bilateralBlur.effect = effect; - g_bilateralBlur.kernels.resize(g_maxKernelSize); - std::vector databuf; - for (size_t n = 1; n <= g_maxKernelSize; n++) { - databuf.resize(n); - // Calculate - double_t sum = 0.0; - for (size_t p = 0; p < n; p++) { - databuf[p] = gaussian1D(p, M_PI); - sum += databuf[p]; - if (p != 0) - sum += databuf[p]; - } - // Normalize - for (size_t p = 0; p < n; p++) { - databuf[p] /= sum; - } - - uint8_t* data = reinterpret_cast(databuf.data()); - const uint8_t** pdata = const_cast(&data); - gs_texture_t* tex = gs_texture_create(n, 1, gs_color_format::GS_R32F, 1, pdata, 0); - if (!tex) { - PLOG_ERROR(" Failed to create bilateral kernel for %d width.", n); - } else { - g_bilateralBlur.kernels[n - 1] = tex; - } - } + makeGaussianKernels(); } } - + // Color Conversion { char* loadError = nullptr; @@ -180,10 +181,10 @@ Filter::Blur::Blur() { g_colorConversionEffect = gs_effect_create_from_file(file, &loadError); bfree(file); if (loadError != nullptr) { - PLOG_ERROR(" Loading color conversion effect failed with error(s): %s", loadError); + P_LOG_ERROR(" Loading color conversion effect failed with error(s): %s", loadError); bfree(loadError); } else if (!g_colorConversionEffect) { - PLOG_ERROR(" Loading color conversion effect failed with unspecified error."); + P_LOG_ERROR(" Loading color conversion effect failed with unspecified error."); } } @@ -199,67 +200,68 @@ Filter::Blur::~Blur() { gs_effect_destroy(g_bilateralBlur.effect); gs_effect_destroy(g_gaussianBlur.effect); for (size_t n = 1; n <= g_maxKernelSize; n++) { - gs_texture_destroy(g_gaussianBlur.kernels[n - 1]); + if (g_gaussianBlur.kernels.size() > 0) + gs_texture_destroy(g_gaussianBlur.kernels[n - 1]); } gs_effect_destroy(g_boxBlurEffect); obs_leave_graphics(); } const char * Filter::Blur::get_name(void *) { - return P_TRANSLATE(P_FILTER_BLUR); + return P_TRANSLATE(S_FILTER_BLUR); } void Filter::Blur::get_defaults(obs_data_t *data) { - obs_data_set_default_int(data, P_FILTER_BLUR_TYPE, Filter::Blur::Type::Box); - obs_data_set_default_int(data, P_FILTER_BLUR_SIZE, 5); + obs_data_set_default_int(data, S_FILTER_BLUR_TYPE, Filter::Blur::Type::Box); + obs_data_set_default_int(data, S_FILTER_BLUR_SIZE, 5); // Bilateral Only - obs_data_set_default_double(data, P_FILTER_BLUR_BILATERAL_SMOOTHING, 50.0); - obs_data_set_default_double(data, P_FILTER_BLUR_BILATERAL_SHARPNESS, 90.0); + obs_data_set_default_double(data, S_FILTER_BLUR_BILATERAL_SMOOTHING, 50.0); + obs_data_set_default_double(data, S_FILTER_BLUR_BILATERAL_SHARPNESS, 90.0); // Advanced - obs_data_set_default_bool(data, P_FILTER_BLUR_ADVANCED, false); - obs_data_set_default_int(data, P_FILTER_BLUR_ADVANCED_COLORFORMAT, ColorFormat::RGB); + obs_data_set_default_bool(data, S_ADVANCED, false); + obs_data_set_default_int(data, S_FILTER_BLUR_COLORFORMAT, ColorFormat::RGB); } obs_properties_t * Filter::Blur::get_properties(void *) { obs_properties_t *pr = obs_properties_create(); obs_property_t* p = NULL; - p = obs_properties_add_list(pr, P_FILTER_BLUR_TYPE, P_TRANSLATE(P_FILTER_BLUR_TYPE), + p = obs_properties_add_list(pr, S_FILTER_BLUR_TYPE, P_TRANSLATE(S_FILTER_BLUR_TYPE), obs_combo_type::OBS_COMBO_TYPE_LIST, obs_combo_format::OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_FILTER_BLUR_TYPE))); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_FILTER_BLUR_TYPE))); obs_property_set_modified_callback(p, modified_properties); - obs_property_list_add_int(p, P_TRANSLATE(P_FILTER_BLUR_TYPE_BOX), + obs_property_list_add_int(p, P_TRANSLATE(S_FILTER_BLUR_TYPE_BOX), Filter::Blur::Type::Box); - obs_property_list_add_int(p, P_TRANSLATE(P_FILTER_BLUR_TYPE_GAUSSIAN), + obs_property_list_add_int(p, P_TRANSLATE(S_FILTER_BLUR_TYPE_GAUSSIAN), Filter::Blur::Type::Gaussian); - obs_property_list_add_int(p, P_TRANSLATE(P_FILTER_BLUR_TYPE_BILATERAL), + obs_property_list_add_int(p, P_TRANSLATE(S_FILTER_BLUR_TYPE_BILATERAL), Filter::Blur::Type::Bilateral); - p = obs_properties_add_int_slider(pr, P_FILTER_BLUR_SIZE, - P_TRANSLATE(P_FILTER_BLUR_SIZE), 1, 25, 1); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_FILTER_BLUR_SIZE))); + p = obs_properties_add_int_slider(pr, S_FILTER_BLUR_SIZE, + P_TRANSLATE(S_FILTER_BLUR_SIZE), 1, 25, 1); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_FILTER_BLUR_SIZE))); obs_property_set_modified_callback(p, modified_properties); // Bilateral Only - p = obs_properties_add_float_slider(pr, P_FILTER_BLUR_BILATERAL_SMOOTHING, - P_TRANSLATE(P_FILTER_BLUR_BILATERAL_SMOOTHING), 0.01, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_FILTER_BLUR_BILATERAL_SMOOTHING))); - p = obs_properties_add_float_slider(pr, P_FILTER_BLUR_BILATERAL_SHARPNESS, - P_TRANSLATE(P_FILTER_BLUR_BILATERAL_SHARPNESS), 0, 99.99, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_FILTER_BLUR_BILATERAL_SHARPNESS))); + p = obs_properties_add_float_slider(pr, S_FILTER_BLUR_BILATERAL_SMOOTHING, + P_TRANSLATE(S_FILTER_BLUR_BILATERAL_SMOOTHING), 0.01, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_FILTER_BLUR_BILATERAL_SMOOTHING))); + p = obs_properties_add_float_slider(pr, S_FILTER_BLUR_BILATERAL_SHARPNESS, + P_TRANSLATE(S_FILTER_BLUR_BILATERAL_SHARPNESS), 0, 99.99, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_FILTER_BLUR_BILATERAL_SHARPNESS))); // Advanced - p = obs_properties_add_bool(pr, P_FILTER_BLUR_ADVANCED, P_TRANSLATE(P_FILTER_BLUR_ADVANCED)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_FILTER_BLUR_ADVANCED))); + p = obs_properties_add_bool(pr, S_ADVANCED, P_TRANSLATE(S_ADVANCED)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_ADVANCED))); obs_property_set_modified_callback(p, modified_properties); - p = obs_properties_add_list(pr, P_FILTER_BLUR_ADVANCED_COLORFORMAT, - P_TRANSLATE(P_FILTER_BLUR_ADVANCED_COLORFORMAT), + p = obs_properties_add_list(pr, S_FILTER_BLUR_COLORFORMAT, + P_TRANSLATE(S_FILTER_BLUR_COLORFORMAT), obs_combo_type::OBS_COMBO_TYPE_LIST, obs_combo_format::OBS_COMBO_FORMAT_INT); obs_property_set_long_description(p, - P_TRANSLATE(P_DESC(P_FILTER_BLUR_ADVANCED_COLORFORMAT))); + P_TRANSLATE(P_DESC(S_FILTER_BLUR_COLORFORMAT))); obs_property_list_add_int(p, "RGB", ColorFormat::RGB); obs_property_list_add_int(p, "YUV", @@ -271,7 +273,7 @@ obs_properties_t * Filter::Blur::get_properties(void *) { bool Filter::Blur::modified_properties(obs_properties_t *pr, obs_property_t *, obs_data_t *d) { bool showBilateral = false; - switch (obs_data_get_int(d, P_FILTER_BLUR_TYPE)) { + switch (obs_data_get_int(d, S_FILTER_BLUR_TYPE)) { case Filter::Blur::Type::Box: break; case Filter::Blur::Type::Gaussian: @@ -282,17 +284,17 @@ bool Filter::Blur::modified_properties(obs_properties_t *pr, obs_property_t *, o } // Bilateral Blur - obs_property_set_visible(obs_properties_get(pr, P_FILTER_BLUR_BILATERAL_SMOOTHING), + obs_property_set_visible(obs_properties_get(pr, S_FILTER_BLUR_BILATERAL_SMOOTHING), showBilateral); - obs_property_set_visible(obs_properties_get(pr, P_FILTER_BLUR_BILATERAL_SHARPNESS), + obs_property_set_visible(obs_properties_get(pr, S_FILTER_BLUR_BILATERAL_SHARPNESS), showBilateral); // Advanced bool showAdvanced = false; - if (obs_data_get_bool(d, P_FILTER_BLUR_ADVANCED)) + if (obs_data_get_bool(d, S_ADVANCED)) showAdvanced = true; - obs_property_set_visible(obs_properties_get(pr, P_FILTER_BLUR_ADVANCED_COLORFORMAT), + obs_property_set_visible(obs_properties_get(pr, S_FILTER_BLUR_COLORFORMAT), showAdvanced); return true; @@ -364,7 +366,7 @@ Filter::Blur::Instance::~Instance() { } void Filter::Blur::Instance::update(obs_data_t *data) { - m_type = (Type)obs_data_get_int(data, P_FILTER_BLUR_TYPE); + m_type = (Type)obs_data_get_int(data, S_FILTER_BLUR_TYPE); switch (m_type) { case Filter::Blur::Type::Box: m_effect = g_boxBlurEffect; @@ -376,14 +378,14 @@ void Filter::Blur::Instance::update(obs_data_t *data) { m_effect = g_bilateralBlur.effect; break; } - m_size = (uint64_t)obs_data_get_int(data, P_FILTER_BLUR_SIZE); + m_size = (uint64_t)obs_data_get_int(data, S_FILTER_BLUR_SIZE); // Bilateral Blur - m_bilateralSmoothing = obs_data_get_double(data, P_FILTER_BLUR_BILATERAL_SMOOTHING) / 100.0; - m_bilateralSharpness = obs_data_get_double(data, P_FILTER_BLUR_BILATERAL_SHARPNESS) / 100.0; + m_bilateralSmoothing = obs_data_get_double(data, S_FILTER_BLUR_BILATERAL_SMOOTHING) / 100.0; + m_bilateralSharpness = obs_data_get_double(data, S_FILTER_BLUR_BILATERAL_SHARPNESS) / 100.0; // Advanced - m_colorFormat = obs_data_get_int(data, P_FILTER_BLUR_ADVANCED_COLORFORMAT); + m_colorFormat = obs_data_get_int(data, S_FILTER_BLUR_COLORFORMAT); } uint32_t Filter::Blur::Instance::get_width() { @@ -406,6 +408,7 @@ void Filter::Blur::Instance::video_tick(float) {} void Filter::Blur::Instance::video_render(gs_effect_t *effect) { bool failed = false; + vec4 black; vec4_zero(&black); obs_source_t *parent = obs_filter_get_parent(m_source), *target = obs_filter_get_target(m_source); @@ -426,7 +429,7 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { #pragma region Source To Texture gs_texrender_reset(m_primaryRT); if (!gs_texrender_begin(m_primaryRT, baseW, baseH)) { - PLOG_ERROR(" Failed to set up base texture."); + P_LOG_ERROR(" Failed to set up base texture."); obs_source_skip_video_filter(m_source); return; } else { @@ -440,11 +443,8 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { // Set up camera stuff gs_set_cull_mode(GS_NEITHER); gs_reset_blend_state(); - gs_blend_function_separate( - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO, - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO); + 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); @@ -454,7 +454,7 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { 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 { - PLOG_ERROR(" Unable to render source."); + P_LOG_ERROR(" Unable to render source."); failed = true; } gs_texrender_end(m_primaryRT); @@ -467,35 +467,31 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { sourceTexture = gs_texrender_get_texture(m_primaryRT); if (!sourceTexture) { - PLOG_ERROR(" Failed to get source texture."); + P_LOG_ERROR(" Failed to get source texture."); obs_source_skip_video_filter(m_source); return; } #pragma endregion Source To Texture // Conversion +#pragma region RGB -> YUV if (m_colorFormat == ColorFormat::YUV) { gs_texrender_reset(m_secondaryRT); if (!gs_texrender_begin(m_secondaryRT, baseW, baseH)) { - PLOG_ERROR(" Failed to set up base texture."); + 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 - vec4 black; - vec4_zero(&black); 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_blend_function_separate( - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO, - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO); + 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); @@ -503,7 +499,7 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { gs_eparam_t* param = gs_effect_get_param_by_name(g_colorConversionEffect, "image"); if (!param) { - PLOG_ERROR(" Failed to set image param."); + P_LOG_ERROR(" Failed to set image param."); failed = true; } else { gs_effect_set_texture(param, sourceTexture); @@ -521,18 +517,77 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { sourceTexture = gs_texrender_get_texture(m_secondaryRT); if (!sourceTexture) { - PLOG_ERROR(" Failed to get source texture."); + P_LOG_ERROR(" Failed to get source texture."); obs_source_skip_video_filter(m_source); return; } } +#pragma endregion RGB -> YUV - gs_texture_t *blurred = blur_render(sourceTexture, baseW, baseH); +#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, *intermediate = sourceTexture; + std::tuple kvs[] = { + std::make_tuple("Horizontal", m_rtHorizontal, 1.0f / baseW, 0.0f), + std::make_tuple("Vertical", m_rtVertical, 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; + switch (m_type) { + case Gaussian: + apply_gaussian_param(); + break; + case Bilateral: + apply_bilateral_param(); + break; + } + + 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(m_effect, "Draw")) { + 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 YUV -> RGB or straight draw // Draw final effect { gs_effect_t* finalEffect = defaultEffect; @@ -543,9 +598,19 @@ void Filter::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) { - PLOG_ERROR(" Failed to set image param."); + P_LOG_ERROR(" Failed to set image param."); failed = true; } else { gs_effect_set_texture(param, blurred); @@ -554,6 +619,7 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { gs_draw_sprite(blurred, 0, baseW, baseH); } } +#pragma endregion YUV -> RGB or straight draw if (failed) { obs_source_skip_video_filter(m_source); @@ -561,175 +627,148 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) { } } -gs_texture_t* Filter::Blur::Instance::blur_render(gs_texture_t* input, uint32_t baseW, uint32_t baseH) { - bool failed = false; - gs_texture_t *intermediate; - -#pragma region Horizontal Pass - gs_texrender_reset(m_rtHorizontal); - if (!gs_texrender_begin(m_rtHorizontal, baseW, baseH)) { - PLOG_ERROR(" Failed to initialize."); - return nullptr; - } else { - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - - // Clear to Black - vec4 black; - vec4_zero(&black); - 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_blend_function_separate( - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO, - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::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); - - // Prepare Effect - if (!apply_effect_param(input, (float)(1.0 / baseW), 0)) { - PLOG_ERROR(" Failed to set effect parameters."); - failed = true; - } else { - while (gs_effect_loop(m_effect, "Draw")) { - gs_draw_sprite(input, 0, baseW, baseH); - } - } - gs_texrender_end(m_rtHorizontal); - } - - if (failed) { - return nullptr; - } - - intermediate = gs_texrender_get_texture(m_rtHorizontal); - if (!intermediate) { - PLOG_ERROR(" Failed to get intermediate texture."); - return nullptr; - } -#pragma endregion Horizontal Pass - -#pragma region Vertical Pass - gs_texrender_reset(m_rtVertical); - if (!gs_texrender_begin(m_rtVertical, baseW, baseH)) { - PLOG_ERROR(" Failed to initialize."); - return nullptr; - } else { - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - - // Clear to Black - vec4 black; - vec4_zero(&black); - 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_blend_function_separate( - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::GS_BLEND_ZERO, - gs_blend_type::GS_BLEND_ONE, - gs_blend_type::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); - - // Prepare Effect - if (!apply_effect_param(intermediate, 0, (float)(1.0 / baseH))) { - PLOG_ERROR(" Failed to set effect parameters."); - failed = true; - } else { - while (gs_effect_loop(m_effect, "Draw")) { - gs_draw_sprite(intermediate, 0, baseW, baseH); - } - } - gs_texrender_end(m_rtVertical); - } - - if (failed) { - return nullptr; - } - - intermediate = gs_texrender_get_texture(m_rtVertical); - if (!intermediate) { - PLOG_ERROR(" Failed to get intermediate texture."); - return nullptr; - } -#pragma endregion Vertical Pass - - return intermediate; +gs_effect_param* gs_effect_get_param(gs_effect_t* effect, const char* name) { + gs_effect_param* p = gs_effect_get_param_by_name(effect, name); + if (!p) + P_LOG_ERROR(" Failed to find parameter %s in effect.", name); + return p; } -bool Filter::Blur::Instance::apply_effect_param(gs_texture_t* texture, float uvTexelX, float uvTexelY) { +bool gs_set_param_int(gs_effect_t* effect, const char* name, int value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_int(p, value); + return true; + } + P_LOG_ERROR(" Failed to set value %d for parameter %s in" + " effect.", value, name); + return false; +} + +bool gs_set_param_float(gs_effect_t* effect, const char* name, float value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_float(p, value); + return true; + } + P_LOG_ERROR(" Failed to set value %f for parameter %s in" + " effect.", value, name); + return false; +} + +bool gs_set_param_float2(gs_effect_t* effect, const char* name, vec2* value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_vec2(p, value); + return true; + } + P_LOG_ERROR(" Failed to set value {%f,%f} for parameter %s" + " in effect.", value->x, value->y, name); + return false; +} + +bool gs_set_param_float3(gs_effect_t* effect, const char* name, vec3* value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_vec3(p, value); + return true; + } + P_LOG_ERROR(" Failed to set value {%f,%f,%f} for parameter" + "%s in effect.", value->x, value->y, value->z, name); + return false; +} + +bool gs_set_param_float4(gs_effect_t* effect, const char* name, vec4* value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_vec4(p, value); + return true; + } + P_LOG_ERROR(" Failed to set value {%f,%f,%f,%f} for" + " parameter %s in effect.", value->x, value->y, value->z, + value->w, name); + return false; +} + +bool gs_set_param_texture(gs_effect_t* effect, const char* name, gs_texture_t* value) { + gs_effect_param* p = nullptr; + if (nullptr != (p = gs_effect_get_param(effect, name))) { + gs_effect_set_texture(p, value); + return true; + } + P_LOG_ERROR(" Failed to set texture for" + " parameter %s in effect.", name); + return false; +} + +bool Filter::Blur::Instance::apply_shared_param(gs_texture_t* input, float texelX, float texelY) { + bool result = true; + + result = result && gs_set_param_texture(m_effect, "u_image", input); + + vec2 imageSize; + vec2_set(&imageSize, + (float)gs_texture_get_width(input), + (float)gs_texture_get_height(input)); + result = result && gs_set_param_float2(m_effect, "u_imageSize", &imageSize); + + vec2 imageTexelDelta; + vec2_set(&imageTexelDelta, 1.0f, 1.0f); + vec2_div(&imageTexelDelta, &imageTexelDelta, &imageSize); + result = result && gs_set_param_float2(m_effect, "u_imageTexelDelta", &imageTexelDelta); + + vec2 texel; vec2_set(&texel, texelX, texelY); + result = result && gs_set_param_float2(m_effect, "u_texelDelta", &texel); + + result = result && gs_set_param_int(m_effect, "u_radius", (int)m_size); + result = result && gs_set_param_int(m_effect, "u_diameter", (int)(1 + (m_size * 2))); + + return result; +} + +bool Filter::Blur::Instance::apply_bilateral_param() { gs_eparam_t *param; - // UV Stepping - param = gs_effect_get_param_by_name(m_effect, "texel"); - if (!param) { - PLOG_ERROR(" Failed to set texel param."); + if (m_type != Type::Bilateral) return false; - } else { - PLOG_DEBUG(" Applying texel parameter."); - vec2 texel; - vec2_set(&texel, uvTexelX, uvTexelY); - gs_effect_set_vec2(param, &texel); - } - - // Filter Width - param = gs_effect_get_param_by_name(m_effect, "widthHalf"); - if (!param) { - PLOG_ERROR(" Failed to set widthHalf param."); - return false; - } else { - PLOG_DEBUG(" Applying widthHalf parameter."); - gs_effect_set_int(param, (int)m_size); - } - param = gs_effect_get_param_by_name(m_effect, "width"); - if (!param) { - PLOG_ERROR(" Failed to set width param."); - return false; - } else { - PLOG_DEBUG(" Applying width parameter."); - gs_effect_set_int(param, (int)(1 + m_size * 2)); - } - - // Texture - param = gs_effect_get_param_by_name(m_effect, "image"); - if (!param) { - PLOG_ERROR(" Failed to set image param."); - return false; - } else { - PLOG_DEBUG(" Applying image parameter."); - gs_effect_set_texture(param, texture); - } // Bilateral Blur - if (m_type == Type::Bilateral) { - param = gs_effect_get_param_by_name(m_effect, "bilateralSmoothing"); - if (!param) { - PLOG_ERROR(" Failed to set bilateralSmoothing param."); - return false; - } else { - PLOG_DEBUG(" Applying bilateralSmoothing parameter."); - gs_effect_set_float(param, - (float)(m_bilateralSmoothing * (1 + m_size * 2))); - } + param = gs_effect_get_param_by_name(m_effect, "bilateralSmoothing"); + if (!param) { + P_LOG_ERROR(" Failed to set bilateralSmoothing param."); + return false; + } else { + P_LOG_DEBUG(" Applying bilateralSmoothing parameter."); + gs_effect_set_float(param, + (float)(m_bilateralSmoothing * (1 + m_size * 2))); + } - param = gs_effect_get_param_by_name(m_effect, "bilateralSharpness"); - if (!param) { - PLOG_ERROR(" Failed to set bilateralSmoothing param."); - return false; - } else { - PLOG_DEBUG(" Applying bilateralSharpness parameter."); - gs_effect_set_float(param, (float)(1.0 - m_bilateralSharpness)); - } + param = gs_effect_get_param_by_name(m_effect, "bilateralSharpness"); + if (!param) { + P_LOG_ERROR(" Failed to set bilateralSmoothing param."); + return false; + } else { + P_LOG_DEBUG(" Applying bilateralSharpness parameter."); + gs_effect_set_float(param, (float)(1.0 - m_bilateralSharpness)); } return true; } + +bool Filter::Blur::Instance::apply_gaussian_param() { + bool result = true; + + if (m_type != Type::Gaussian) + return false; + + if (g_gaussianBlur.kernels.size() >= m_size) { + result = result && gs_set_param_texture(m_effect, "kernel", + g_gaussianBlur.kernels[m_size - 1]); + } + + vec2 kerneltexel; + vec2_set(&kerneltexel, 1.0f / (m_size + 1), 0); + result = result && gs_set_param_float2(m_effect, "kernelTexel", &kerneltexel); + + return result; +} diff --git a/source/filter-blur.h b/source/filter-blur.h index 05400af5..a2b79bfb 100644 --- a/source/filter-blur.h +++ b/source/filter-blur.h @@ -21,20 +21,19 @@ #include "plugin.h" #include "gs-helper.h" -#define P_FILTER_BLUR "Filter.Blur" -#define P_FILTER_BLUR_TYPE "Filter.Blur.Type" -#define P_FILTER_BLUR_TYPE_BOX "Filter.Blur.Type.Box" -#define P_FILTER_BLUR_TYPE_GAUSSIAN "Filter.Blur.Type.Gaussian" -#define P_FILTER_BLUR_TYPE_BILATERAL "Filter.Blur.Type.Bilateral" -#define P_FILTER_BLUR_SIZE "Filter.Blur.Size" +#define S_FILTER_BLUR "Filter.Blur" +#define S_FILTER_BLUR_TYPE "Filter.Blur.Type" +#define S_FILTER_BLUR_TYPE_BOX "Filter.Blur.Type.Box" +#define S_FILTER_BLUR_TYPE_GAUSSIAN "Filter.Blur.Type.Gaussian" +#define S_FILTER_BLUR_TYPE_BILATERAL "Filter.Blur.Type.Bilateral" +#define S_FILTER_BLUR_SIZE "Filter.Blur.Size" // Bilateral Blur -#define P_FILTER_BLUR_BILATERAL_SMOOTHING "Filter.Blur.Bilateral.Smoothing" -#define P_FILTER_BLUR_BILATERAL_SHARPNESS "Filter.Blur.Bilateral.Sharpness" +#define S_FILTER_BLUR_BILATERAL_SMOOTHING "Filter.Blur.Bilateral.Smoothing" +#define S_FILTER_BLUR_BILATERAL_SHARPNESS "Filter.Blur.Bilateral.Sharpness" // Advanced -#define P_FILTER_BLUR_ADVANCED "Filter.Blur.Advanced" -#define P_FILTER_BLUR_ADVANCED_COLORFORMAT "Filter.Blur.Avanced.ColorFormat" +#define S_FILTER_BLUR_COLORFORMAT "Filter.Blur.ColorFormat" namespace Filter { class Blur { @@ -45,7 +44,8 @@ namespace Filter { static const char *get_name(void *); static void get_defaults(obs_data_t *); static obs_properties_t *get_properties(void *); - static bool modified_properties(obs_properties_t *, obs_property_t *, obs_data_t *); + static bool modified_properties(obs_properties_t *, + obs_property_t *, obs_data_t *); static void *create(obs_data_t *, obs_source_t *); static void destroy(void *); @@ -83,10 +83,10 @@ namespace Filter { void hide(); void video_tick(float); void video_render(gs_effect_t*); - gs_texture_t* blur_render(gs_texture_t* input, uint32_t baseW, uint32_t baseH); - - bool apply_effect_param(gs_texture_t* texture, - float uvTexelX, float uvTexelY); + bool apply_shared_param(gs_texture_t* input, + float texelX, float texelY); + bool apply_bilateral_param(); + bool apply_gaussian_param(); private: obs_source_t *m_source;