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
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2019-04-02 03:02:57 +02:00 committed by Michael Fabian Dirks
parent b6d45ce73c
commit 7c4d46c1fe
3 changed files with 510 additions and 0 deletions

View file

@ -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);
}
}

View file

@ -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 <obs-module.h>
#include <obs.h>
#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("<gfx::blur::box_linear> 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<std::mutex> 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::rendertarget>(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<gs::texture> 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<gs::texture> 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();
}

View file

@ -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 <cinttypes>
#include <memory>
#include <mutex>
#include <vector>
#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<gs::texture> m_input_texture;
std::vector<std::shared_ptr<gs::rendertarget>> 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