filter-blur: Use only a single Gaussian Kernel Texture

This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2018-04-28 14:41:18 +02:00
parent 6c1e874369
commit 1ba9145fbd
3 changed files with 77 additions and 147 deletions

View File

@ -14,20 +14,13 @@ uniform float2 u_texelDelta;
uniform texture2d kernel; uniform texture2d kernel;
uniform float2 kernelTexel; uniform float2 kernelTexel;
sampler_state pointClampSampler { sampler_state textureSampler {
Filter = Point; Filter = Point;
AddressU = Clamp; AddressU = Clamp;
AddressV = Clamp; AddressV = Clamp;
MinLOD = 0; MinLOD = 0;
MaxLOD = 0; MaxLOD = 0;
}; };
sampler_state bilinearClampSampler {
Filter = Bilinear;
AddressU = Clamp;
AddressV = Clamp;
MinLOD = 0;
MaxLOD = 0;
};
struct VertDataIn { struct VertDataIn {
float4 pos : POSITION; float4 pos : POSITION;
@ -47,81 +40,18 @@ VertDataOut VSDefault(VertDataIn v_in)
return vert_out; return vert_out;
} }
// Gaussian Blur
float Gaussian(float x, float o) {
const float pivalue = 3.1415926535897932384626433832795;
return (1.0 / (o * sqrt(2.0 * pivalue))) * exp((-(x * x)) / (2 * (o * o)));
}
float4 InternalGaussian(float2 p_uv, float2 p_uvStep, int p_radius,
texture2d p_image, float2 p_imageTexel) {
float l_gauss = Gaussian(0, p_size);
float4 l_value = p_image.Sample(pointClampSampler, p_uv) * gauss;
float2 l_uvoffset = float2(0, 0);
for (int k = 1; k <= p_size; k++) {
l_uvoffset += p_uvStep;
float l_g = Gaussian(k, p_size);
float4 l_p = p_image.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g;
float4 l_n = p_image.Sample(pointClampSampler, p_uv - l_uvoffset) * l_g;
l_value += l_p + l_n;
l_gauss += l_g;
}
l_value = l_value * (1.0 / l_gauss);
return l_value;
}
float4 InternalGaussianPrecalculated(float2 p_uv, float2 p_uvStep, int p_radius,
texture2d p_image, float2 p_imageTexel,
texture2d p_kernel, float2 p_kernelTexel) {
float4 l_value = p_image.Sample(pointClampSampler, p_uv)
* kernel.Sample(pointClampSampler, float2(0, 0)).r;
float2 l_uvoffset = float2(0, 0);
for (int k = 1; k <= p_radius; k++) {
l_uvoffset += p_uvStep;
float l_g = p_kernel.Sample(pointClampSampler, p_kernelTexel * k).r;
float4 l_p = p_image.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g;
float4 l_n = p_image.Sample(pointClampSampler, p_uv - l_uvoffset) * l_g;
l_value += l_p + l_n;
}
return l_value;
}
/*float4 InternalGaussianPrecalculatedNVOptimized(float2 p_uv, int p_size,
texture2d p_image, float2 p_imageTexel,
texture2d p_kernel, float2 p_kernelTexel) {
if (p_size % 2 == 0) {
float4 l_value = p_image.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_image.Sample(pointClampSampler, p_uv + l_uvoffset) * l_g;
float4 l_n = p_image.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_image, p_texel, p_size, p_kernel, p_kerneltexel);)
}
}*/
float4 PSGaussian(VertDataOut v_in) : TARGET { float4 PSGaussian(VertDataOut v_in) : TARGET {
//return InternalGaussian(v_in.uv, u_texelDelta, u_image, u_imageTexel, u_radius); float2 uvOffset = float2(0, 0);
float4 rgba = u_image.SampleLevel(textureSampler, v_in.uv, 0)
///* * kernel.SampleLevel(textureSampler, (float2(0, u_radius - 1) * kernelTexel), 0).r;
return InternalGaussianPrecalculated( for (int k = 1; k <= u_radius; k++) {
v_in.uv, u_texelDelta, u_radius, uvOffset += u_texelDelta;
u_image, u_imageTexel, float l_g = kernel.SampleLevel(textureSampler, (float2(k, u_radius - 1) * kernelTexel), 0).r;
kernel, kernelTexel);//*/ float4 l_p = u_image.SampleLevel(textureSampler, v_in.uv + uvOffset, 0) * l_g;
float4 l_n = u_image.SampleLevel(textureSampler, v_in.uv - uvOffset, 0) * l_g;
/* rgba += l_p + l_n;
return InternalGaussianPrecalculatedNVOptimize( }
v_in.uv, u_texelDelta, u_radius, return rgba;
u_image, u_imageTexel,
kernel, kernelTexel);//*/
} }
technique Draw technique Draw

View File

@ -22,6 +22,7 @@
#include "util-math.h" #include "util-math.h"
#include <math.h> #include <math.h>
#include <map> #include <map>
#include <inttypes.h>
extern "C" { extern "C" {
#pragma warning (push) #pragma warning (push)
@ -63,50 +64,6 @@ enum ColorFormat : uint64_t {
}; };
// Global Data // Global Data
const size_t MaxKernelSize = 25;
std::map<std::string, std::shared_ptr<gs::effect>> g_effects;
std::vector<std::shared_ptr<gs::texture>> g_gaussianKernels;
double_t bilateral(double_t x, double_t o) {
return 0.39894 * exp(-0.5 * (x * x) / (o * o)) / o;
}
static void GenerateGaussianKernelTextures() {
size_t textureBufferSize = GetNearestPowerOfTwoAbove(MaxKernelSize);
g_gaussianKernels.resize(MaxKernelSize);
std::vector<double_t> mathBuffer(MaxKernelSize + 1);
std::vector<float_t> textureBuffer(textureBufferSize);
for (size_t n = 0; n < MaxKernelSize; n++) {
size_t width = 2 + n;
// Generate Gaussian Gradient and calculate sum.
double_t sum = 0;
for (size_t p = 0; p < width; p++) {
mathBuffer[p] = Gaussian1D(double_t(p), double_t(n + 1));
sum += mathBuffer[p] * (p > 0 ? 2 : 1);
}
// Normalize
double_t inverseSum = 1.0 / sum;
for (size_t p = 0; p < width; p++) {
textureBuffer[p] = float_t(mathBuffer[p] * inverseSum);
}
// Create Texture
uint8_t* data = reinterpret_cast<uint8_t*>(textureBuffer.data());
const uint8_t** pdata = const_cast<const uint8_t**>(&data);
try {
std::shared_ptr<gs::texture> tex = std::make_shared<gs::texture>((uint32_t)textureBufferSize, 1,
gs_color_format::GS_R32F, 1, pdata, 0);
g_gaussianKernels[n] = tex;
} catch (std::runtime_error ex) {
P_LOG_ERROR("<filter-blur> Failed to create gaussian kernel for %d width.", n);
}
}
}
Filter::Blur::Blur() { Filter::Blur::Blur() {
memset(&m_sourceInfo, 0, sizeof(obs_source_info)); memset(&m_sourceInfo, 0, sizeof(obs_source_info));
m_sourceInfo.id = "obs-stream-effects-filter-blur"; m_sourceInfo.id = "obs-stream-effects-filter-blur";
@ -135,7 +92,7 @@ Filter::Blur::Blur() {
for (auto& kv : effects) { for (auto& kv : effects) {
try { try {
std::shared_ptr<gs::effect> effect = std::make_shared<gs::effect>(kv.second); std::shared_ptr<gs::effect> effect = std::make_shared<gs::effect>(kv.second);
g_effects.insert(std::make_pair(kv.first, effect)); m_effects.insert(std::make_pair(kv.first, effect));
} catch (std::runtime_error ex) { } catch (std::runtime_error ex) {
P_LOG_ERROR("<filter-blur> Loading effect '%s' (path: '%s') failed with error(s): %s", P_LOG_ERROR("<filter-blur> Loading effect '%s' (path: '%s') failed with error(s): %s",
kv.first.c_str(), kv.second.c_str(), ex.what()); kv.first.c_str(), kv.second.c_str(), ex.what());
@ -143,15 +100,52 @@ Filter::Blur::Blur() {
} }
} }
GenerateGaussianKernelTextures(); generate_kernel_textures();
obs_leave_graphics(); obs_leave_graphics();
obs_register_source(&m_sourceInfo); obs_register_source(&m_sourceInfo);
} }
Filter::Blur::~Blur() { Filter::Blur::~Blur() {
g_effects.clear(); m_effects.clear();
g_gaussianKernels.clear(); }
void Filter::Blur::generate_gaussian_kernels() {
// 2D texture, horizontal is value, vertical is kernel size.
size_t textureSizePOT = GetNearestPowerOfTwoAbove(max_kernel_size);
std::vector<float_t> textureBuffer(textureSizePOT * textureSizePOT);
std::vector<float_t> mathBuffer(textureSizePOT);
for (size_t width = 1; width <= max_kernel_size; width++) {
size_t v = (width - 1) * textureSizePOT;
// Calculate and normalize
float_t sum = 0;
for (size_t p = 0; p <= width; p++) {
mathBuffer[p] = float_t(Gaussian1D(double_t(p), double_t(width)));
sum += mathBuffer[p] * (p > 0 ? 2 : 1);
}
// Normalize to Texture Buffer
double_t inverseSum = 1.0 / sum;
for (size_t p = 0; p <= width; p++) {
textureBuffer[v + p] = float_t(mathBuffer[p] * inverseSum);
}
}
// Create Texture
try {
auto buf = reinterpret_cast<uint8_t*>(textureBuffer.data());
auto rbuf = const_cast<const uint8_t**>(&buf);
m_gaussianKernelTexture = std::make_shared<gs::texture>(uint32_t(textureSizePOT), uint32_t(textureSizePOT), GS_R32F, 1, rbuf, 0);
} catch (std::runtime_error ex) {
P_LOG_ERROR("<filter-blur> Failed to create gaussian kernel texture.");
}
}
void Filter::Blur::generate_kernel_textures() {
generate_gaussian_kernels();
} }
const char * Filter::Blur::get_name(void *) { const char * Filter::Blur::get_name(void *) {
@ -271,7 +265,7 @@ void Filter::Blur::video_render(void *ptr, gs_effect_t *effect) {
Filter::Blur::Instance::Instance(obs_data_t *data, obs_source_t *context) : m_source(context) { Filter::Blur::Instance::Instance(obs_data_t *data, obs_source_t *context) : m_source(context) {
obs_enter_graphics(); obs_enter_graphics();
m_effect = g_effects.at("Box Blur"); m_effect = filterBlurInstance->m_effects.at("Box Blur");
m_primaryRT = gs_texrender_create(GS_RGBA, GS_ZS_NONE); m_primaryRT = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
m_secondaryRT = gs_texrender_create(GS_RGBA, GS_ZS_NONE); m_secondaryRT = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
m_rtHorizontal = gs_texrender_create(GS_RGBA, GS_ZS_NONE); m_rtHorizontal = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
@ -303,13 +297,13 @@ void Filter::Blur::Instance::update(obs_data_t *data) {
m_type = (Type)obs_data_get_int(data, S_TYPE); m_type = (Type)obs_data_get_int(data, S_TYPE);
switch (m_type) { switch (m_type) {
case Filter::Blur::Type::Box: case Filter::Blur::Type::Box:
m_effect = g_effects.at("Box Blur"); m_effect = filterBlurInstance->m_effects.at("Box Blur");
break; break;
case Filter::Blur::Type::Gaussian: case Filter::Blur::Type::Gaussian:
m_effect = g_effects.at("Gaussian Blur"); m_effect = filterBlurInstance->m_effects.at("Gaussian Blur");
break; break;
case Filter::Blur::Type::Bilateral: case Filter::Blur::Type::Bilateral:
m_effect = g_effects.at("Bilateral Blur"); m_effect = filterBlurInstance->m_effects.at("Bilateral Blur");
break; break;
} }
m_size = (uint64_t)obs_data_get_int(data, S_SIZE); m_size = (uint64_t)obs_data_get_int(data, S_SIZE);
@ -345,7 +339,7 @@ void Filter::Blur::Instance::video_render(gs_effect_t *effect) {
uint32_t uint32_t
baseW = obs_source_get_base_width(target), baseW = obs_source_get_base_width(target),
baseH = obs_source_get_base_height(target); baseH = obs_source_get_base_height(target);
gs_effect_t* colorConversionEffect = g_effects.count("Color Conversion") ? g_effects.at("Color Conversion")->get_object() : nullptr; gs_effect_t* colorConversionEffect = filterBlurInstance->m_effects.count("Color Conversion") ? filterBlurInstance->m_effects.at("Color Conversion")->get_object() : nullptr;
// Skip rendering if our target, parent or context is not valid. // Skip rendering if our target, parent or context is not valid.
if (!target || !parent || !m_source) { if (!target || !parent || !m_source) {
@ -615,20 +609,18 @@ bool Filter::Blur::Instance::apply_bilateral_param() {
} }
bool Filter::Blur::Instance::apply_gaussian_param() { bool Filter::Blur::Instance::apply_gaussian_param() {
bool result = true; if (m_effect->has_parameter("kernel")) {
m_effect->get_parameter("kernel").set_texture(filterBlurInstance->m_gaussianKernelTexture);
if (m_type != Type::Gaussian) } else {
return false; return false;
std::shared_ptr<gs::texture> tex;
if ((m_size - 1) < MaxKernelSize) {
tex = g_gaussianKernels[size_t(m_size - 1)];
result = result && gs_set_param_texture(m_effect->get_object(), "kernel", tex->get_object());
} }
vec2 kerneltexel; if (m_effect->has_parameter("kernelTexel")) {
vec2_set(&kerneltexel, 1.0f / gs_texture_get_width(tex->get_object()), 0); auto tex = filterBlurInstance->m_gaussianKernelTexture->get_object();
result = result && gs_set_param_float2(m_effect->get_object(), "kernelTexel", &kerneltexel); float_t wb = 1.0f / gs_texture_get_width(tex);
float_t hb = 1.0f / gs_texture_get_height(tex);
m_effect->get_parameter("kernelTexel").set_float2(wb, hb);
}
return result; return true;
} }

View File

@ -31,6 +31,9 @@ namespace Filter {
Blur(); Blur();
~Blur(); ~Blur();
void generate_gaussian_kernels();
void generate_kernel_textures();
public: public:
enum Type : int64_t { enum Type : int64_t {
Box, Box,
@ -38,9 +41,14 @@ namespace Filter {
Bilateral, Bilateral,
}; };
std::shared_ptr<gs::texture> m_gaussianKernelTexture;
std::map<std::string, std::shared_ptr<gs::effect>> m_effects;
private: private:
obs_source_info m_sourceInfo; obs_source_info m_sourceInfo;
static const size_t max_kernel_size = 25;
public /*static*/: public /*static*/:
static const char *get_name(void *); static const char *get_name(void *);
static void get_defaults(obs_data_t *); static void get_defaults(obs_data_t *);