mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-12-28 18:41:14 +00:00
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:
parent
b6d45ce73c
commit
7c4d46c1fe
3 changed files with 510 additions and 0 deletions
79
data/effects/blur/dual-filtering.effect
Normal file
79
data/effects/blur/dual-filtering.effect
Normal 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);
|
||||
}
|
||||
}
|
315
source/gfx/blur/gfx-blur-dual-filtering.cpp
Normal file
315
source/gfx/blur/gfx-blur-dual-filtering.cpp
Normal 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();
|
||||
}
|
116
source/gfx/blur/gfx-blur-dual-filtering.hpp
Normal file
116
source/gfx/blur/gfx-blur-dual-filtering.hpp
Normal 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
|
Loading…
Reference in a new issue