From 7c4d46c1fe5fb28fb8d7e5ee2bcaa7c5053001b4 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Tue, 2 Apr 2019 03:02:57 +0200 Subject: [PATCH] gfx/blur/dual-filtering: Implement Dual Filtering Blur Dual Filtering (or Dual Kawase) is an approximation of Gaussian Blur that can reach much higher Blur sizes at a much lower cost. However it is locked to a 2^n size, which means that currently it isn't possible to use it for blur sizes like 19, 24 and 31. The Blur works by using the linear sampling of a GPU, combined with down- and upsampling and carefully placed sampling points. This means that there is no need for a linear optimized version of this Blur. Related: #45, #6 --- data/effects/blur/dual-filtering.effect | 79 +++++ source/gfx/blur/gfx-blur-dual-filtering.cpp | 315 ++++++++++++++++++++ source/gfx/blur/gfx-blur-dual-filtering.hpp | 116 +++++++ 3 files changed, 510 insertions(+) create mode 100644 data/effects/blur/dual-filtering.effect create mode 100644 source/gfx/blur/gfx-blur-dual-filtering.cpp create mode 100644 source/gfx/blur/gfx-blur-dual-filtering.hpp diff --git a/data/effects/blur/dual-filtering.effect b/data/effects/blur/dual-filtering.effect new file mode 100644 index 0000000..e123e72 --- /dev/null +++ b/data/effects/blur/dual-filtering.effect @@ -0,0 +1,79 @@ +// Parameters: +/// OBS Default +uniform float4x4 ViewProj; +/// Texture +uniform texture2d pImage; +uniform float2 pImageSize; +uniform float2 pImageTexel; +uniform float2 pImageHalfTexel; + +// Sampler +sampler_state linearSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; + MinLOD = 0; + MaxLOD = 0; +}; + +// Default Vertex Shader and Data +struct VertDataIn { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +struct VertDataOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertDataOut VSDefault(VertDataIn vtx) { + VertDataOut vert_out; + vert_out.pos = mul(float4(vtx.pos.xyz, 1.0), ViewProj); + vert_out.uv = vtx.uv; + return vert_out; +} + +// Downsample +float4 PSDown(VertDataOut vtx) : TARGET { + //vtx.uv = ((floor(vtx.uv * pImageSize) + float2(0.5, 0.5)) * pImageTexel); + + float4 pxCC = pImage.Sample(linearSampler, vtx.uv) * 4.0; + float4 pxTL = pImage.Sample(linearSampler, vtx.uv - pImageHalfTexel); + float4 pxTR = pImage.Sample(linearSampler, vtx.uv + pImageHalfTexel); + float4 pxBL = pImage.Sample(linearSampler, vtx.uv + float2(pImageHalfTexel.x, -pImageHalfTexel.y)); + float4 pxBR = pImage.Sample(linearSampler, vtx.uv - float2(pImageHalfTexel.x, -pImageHalfTexel.y)); + + return (pxCC + pxTL + pxTR + pxBL + pxBR) * 0.125; +} + +technique Down { + pass { + vertex_shader = VSDefault(vtx); + pixel_shader = PSDown(vtx); + } +} + +// Upsample +float4 PSUp(VertDataOut vtx) : TARGET { + //vtx.uv = ((floor(vtx.uv * pImageSize) + float2(0.5, 0.5)) * pImageTexel); + + float4 pxL = pImage.Sample(linearSampler, vtx.uv - float2(pImageHalfTexel.x * 2.0, 0.)); + float4 pxBL = pImage.Sample(linearSampler, vtx.uv - float2(pImageHalfTexel.x, -pImageHalfTexel.y)); + float4 pxB = pImage.Sample(linearSampler, vtx.uv + float2(0., pImageHalfTexel.y * 2.0)); + float4 pxBR = pImage.Sample(linearSampler, vtx.uv + pImageHalfTexel); + float4 pxR = pImage.Sample(linearSampler, vtx.uv + float2(pImageHalfTexel.x * 2.0, 0.)); + float4 pxTR = pImage.Sample(linearSampler, vtx.uv + float2(pImageHalfTexel.x, -pImageHalfTexel.y)); + float4 pxT = pImage.Sample(linearSampler, vtx.uv - float2(0., pImageHalfTexel.y * 2.0)); + float4 pxTL = pImage.Sample(linearSampler, vtx.uv - pImageHalfTexel); + + return (((pxTL + pxTR + pxBL + pxBR) * 2.0) + pxL + pxR + pxT + pxB) * 0.083333333333; + // return (((pxTL + pxTR + pxBL + pxBR) * 2.0) + pxL + pxR + pxT + pxB) / 12; +} + +technique Up { + pass { + vertex_shader = VSDefault(vtx); + pixel_shader = PSUp(vtx); + } +} diff --git a/source/gfx/blur/gfx-blur-dual-filtering.cpp b/source/gfx/blur/gfx-blur-dual-filtering.cpp new file mode 100644 index 0000000..14ef490 --- /dev/null +++ b/source/gfx/blur/gfx-blur-dual-filtering.cpp @@ -0,0 +1,315 @@ +// Modern effects for a modern Streamer +// Copyright (C) 2019 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#include "gfx-blur-dual-filtering.hpp" +#include "plugin.hpp" +#include "util-math.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4201) +#endif +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// Dual Filtering Blur +// +// This type of Blur uses downsampling and upsampling and clever math. That makes it less +// controllable compared to other blur, but it can still be worked with. The distance for +// sampling texels has to be adjusted to match the correct value so that lower levels of +// blur than 2^n are possible. +// +// That means that for a blur size of: +// 0: No Iterations, straight copy. +// 1: 1 Iteration (2x), Arm Size 2, Offset Scale 1.0 +// 2: 2 Iteration (4x), Arm Size 3, Offset Scale 0.5 +// 3: 2 Iteration (4x), Arm Size 4, Offset Scale 1.0 +// 4: 3 Iteration (8x), Arm Size 5, Offset Scale 0.25 +// 5: 3 Iteration (8x), Arm Size 6, Offset Scale 0.5 +// 6: 3 Iteration (8x), Arm Size 7, Offset Scale 0.75 +// 7: 3 Iteration (8x), Arm Size 8, Offset Scale 1.0 +// ... + +#define MAX_LEVELS 16 + +gfx::blur::dual_filtering_data::dual_filtering_data() +{ + try { + char* file = obs_module_file("effects/blur/dual-filtering.effect"); + m_effect = std::make_shared<::gs::effect>(file); + bfree(file); + } catch (...) { + P_LOG_ERROR(" Failed to load effect."); + } +} + +gfx::blur::dual_filtering_data::~dual_filtering_data() +{ + m_effect.reset(); +} + +std::shared_ptr<::gs::effect> gfx::blur::dual_filtering_data::get_effect() +{ + return m_effect; +} + +gfx::blur::dual_filtering_factory::dual_filtering_factory() {} + +gfx::blur::dual_filtering_factory::~dual_filtering_factory() {} + +bool gfx::blur::dual_filtering_factory::is_type_supported(::gfx::blur::type type) +{ + switch (type) { + case ::gfx::blur::type::Area: + return true; + default: + return false; + } +} + +std::shared_ptr<::gfx::blur::ibase> gfx::blur::dual_filtering_factory::create(::gfx::blur::type type) +{ + switch (type) { + case ::gfx::blur::type::Area: + return std::make_shared<::gfx::blur::dual_filtering>(); + default: + throw std::runtime_error("Invalid type."); + } +} + +double_t gfx::blur::dual_filtering_factory::get_min_size(::gfx::blur::type) +{ + return double_t(1.); +} + +double_t gfx::blur::dual_filtering_factory::get_step_size(::gfx::blur::type) +{ + return double_t(1.); +} + +double_t gfx::blur::dual_filtering_factory::get_max_size(::gfx::blur::type) +{ + return double_t(MAX_LEVELS); +} + +double_t gfx::blur::dual_filtering_factory::get_min_angle(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_step_angle(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_max_angle(::gfx::blur::type) +{ + return double_t(0); +} + +bool gfx::blur::dual_filtering_factory::is_step_scale_supported(::gfx::blur::type) +{ + return false; +} + +double_t gfx::blur::dual_filtering_factory::get_min_step_scale_x(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_step_step_scale_x(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_max_step_scale_x(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_min_step_scale_y(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_step_step_scale_y(::gfx::blur::type) +{ + return double_t(0); +} + +double_t gfx::blur::dual_filtering_factory::get_max_step_scale_y(::gfx::blur::type) +{ + return double_t(0); +} + +std::shared_ptr<::gfx::blur::dual_filtering_data> gfx::blur::dual_filtering_factory::data() +{ + std::unique_lock ulock(m_data_lock); + std::shared_ptr<::gfx::blur::dual_filtering_data> data = m_data.lock(); + if (!data) { + data = std::make_shared<::gfx::blur::dual_filtering_data>(); + m_data = data; + } + return data; +} + +::gfx::blur::dual_filtering_factory& gfx::blur::dual_filtering_factory::get() +{ + static ::gfx::blur::dual_filtering_factory instance; + return instance; +} + +gfx::blur::dual_filtering::dual_filtering() + : m_size(0), m_size_iterations(0), m_data(::gfx::blur::dual_filtering_factory::get().data()) +{ + obs_enter_graphics(); + m_rendertargets.resize(MAX_LEVELS + 1); + for (size_t n = 0; n <= MAX_LEVELS; n++) { + m_rendertargets[n] = std::make_shared(GS_RGBA32F, GS_ZS_NONE); + } + obs_leave_graphics(); +} + +gfx::blur::dual_filtering::~dual_filtering() {} + +void gfx::blur::dual_filtering::set_input(std::shared_ptr<::gs::texture> texture) +{ + m_input_texture = texture; +} + +::gfx::blur::type gfx::blur::dual_filtering::get_type() +{ + return ::gfx::blur::type::Area; +} + +double_t gfx::blur::dual_filtering::get_size() +{ + return m_size; +} + +void gfx::blur::dual_filtering::set_size(double_t width) +{ + m_size = width; + m_size_iterations = size_t(round(width)); + if (m_size_iterations >= MAX_LEVELS) { + m_size_iterations = MAX_LEVELS; + } +} + +void gfx::blur::dual_filtering::set_step_scale(double_t, double_t) {} + +void gfx::blur::dual_filtering::get_step_scale(double_t&, double_t&) {} + +std::shared_ptr<::gs::texture> gfx::blur::dual_filtering::render() +{ + auto effect = m_data->get_effect(); + if (!effect) { + return m_input_texture; + } + + size_t actual_iterations = m_size_iterations; + + obs_enter_graphics(); + 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); + + // Downsample + for (size_t n = 1; n <= actual_iterations; n++) { + // Idx 0 is a simply considered as a straight copy of the original and not rendered to. + + // Select Texture + std::shared_ptr tex_cur; + if (n > 1) { + tex_cur = m_rendertargets[n - 1]->get_texture(); + } else { + tex_cur = m_input_texture; + } + + // Reduce Size + uint32_t width = tex_cur->get_width() / 2; + uint32_t height = tex_cur->get_height() / 2; + if ((width <= 0) || (height <= 0)) { + actual_iterations = n - 1; + break; + } + + // Apply + effect->get_parameter("pImage").set_texture(tex_cur); + effect->get_parameter("pImageSize").set_float2(float_t(width), float_t(height)); + effect->get_parameter("pImageTexel").set_float2(1.0f / width, 1.0f / height); + effect->get_parameter("pImageHalfTexel").set_float2(0.5f / width, 0.5f / height); + + { + auto op = m_rendertargets[n]->render(width, height); + gs_ortho(0., 1., 0., 1., 0., 1.); + while (gs_effect_loop(effect->get_object(), "Down")) { + gs_draw_sprite(tex_cur->get_object(), 0, 1, 1); + } + } + } + + // Upsample + for (size_t n = actual_iterations; n > 0; n--) { + // Select Texture + std::shared_ptr tex_cur = m_rendertargets[n]->get_texture(); + + // Get Size + uint32_t width = tex_cur->get_width(); + uint32_t height = tex_cur->get_height(); + + // Apply + effect->get_parameter("pImage").set_texture(tex_cur); + effect->get_parameter("pImageSize").set_float2(float_t(width), float_t(height)); + effect->get_parameter("pImageTexel").set_float2(1.0f / width, 1.0f / height); + effect->get_parameter("pImageHalfTexel").set_float2(0.5f / width, 0.5f / height); + + // Increase Size + width *= 2; + height *= 2; + + { + auto op = m_rendertargets[n - 1]->render(width, height); + gs_ortho(0., 1., 0., 1., 0., 1.); + while (gs_effect_loop(effect->get_object(), "Up")) { + gs_draw_sprite(tex_cur->get_object(), 0, 1, 1); + } + } + } + + gs_blend_state_pop(); + obs_leave_graphics(); + + return m_rendertargets[0]->get_texture(); +} + +std::shared_ptr<::gs::texture> gfx::blur::dual_filtering::get() +{ + return m_rendertargets[0]->get_texture(); +} diff --git a/source/gfx/blur/gfx-blur-dual-filtering.hpp b/source/gfx/blur/gfx-blur-dual-filtering.hpp new file mode 100644 index 0000000..4fb35e4 --- /dev/null +++ b/source/gfx/blur/gfx-blur-dual-filtering.hpp @@ -0,0 +1,116 @@ +// Modern effects for a modern Streamer +// Copyright (C) 2019 Michael Fabian Dirks +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +#pragma once +#include +#include +#include +#include +#include "gfx-blur-base.hpp" +#include "obs/gs/gs-effect.hpp" +#include "obs/gs/gs-rendertarget.hpp" +#include "obs/gs/gs-texture.hpp" + +namespace gfx { + namespace blur { + class dual_filtering_data { + std::shared_ptr<::gs::effect> m_effect; + + public: + dual_filtering_data(); + ~dual_filtering_data(); + + std::shared_ptr<::gs::effect> get_effect(); + }; + + class dual_filtering_factory : public ::gfx::blur::ifactory { + std::mutex m_data_lock; + std::weak_ptr<::gfx::blur::dual_filtering_data> m_data; + + public: + dual_filtering_factory(); + virtual ~dual_filtering_factory(); + + virtual bool is_type_supported(::gfx::blur::type type) override; + + virtual std::shared_ptr<::gfx::blur::ibase> create(::gfx::blur::type type) override; + + virtual double_t get_min_size(::gfx::blur::type type) override; + + virtual double_t get_step_size(::gfx::blur::type type) override; + + virtual double_t get_max_size(::gfx::blur::type type) override; + + virtual double_t get_min_angle(::gfx::blur::type type) override; + + virtual double_t get_step_angle(::gfx::blur::type type) override; + + virtual double_t get_max_angle(::gfx::blur::type type) override; + + virtual bool is_step_scale_supported(::gfx::blur::type type) override; + + virtual double_t get_min_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_step_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_max_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_min_step_scale_y(::gfx::blur::type type) override; + + virtual double_t get_step_step_scale_y(::gfx::blur::type type) override; + + virtual double_t get_max_step_scale_y(::gfx::blur::type type) override; + + std::shared_ptr<::gfx::blur::dual_filtering_data> data(); + + public: // Singleton + static ::gfx::blur::dual_filtering_factory& get(); + }; + + class dual_filtering : public ::gfx::blur::ibase { + std::shared_ptr<::gfx::blur::dual_filtering_data> m_data; + + double_t m_size; + size_t m_size_iterations; + + std::shared_ptr m_input_texture; + + std::vector> m_rendertargets; + + public: + dual_filtering(); + virtual ~dual_filtering(); + + virtual void set_input(std::shared_ptr<::gs::texture> texture) override; + + virtual ::gfx::blur::type get_type() override; + + virtual double_t get_size() override; + + virtual void set_size(double_t width) override; + + virtual void set_step_scale(double_t x, double_t y) override; + + virtual void get_step_scale(double_t& x, double_t& y) override; + + virtual std::shared_ptr<::gs::texture> render() override; + + virtual std::shared_ptr<::gs::texture> get() override; + }; + }; // namespace blur + +}; // namespace gfx