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.
This commit is contained in:
Michael Fabian Dirks 2017-08-20 00:19:58 +02:00
parent 374d1183ea
commit 4dbf414214
5 changed files with 450 additions and 329 deletions

View File

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

View File

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

View File

@ -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

View File

@ -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<float_t> textureBuffer;
std::vector<double_t> 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<uint8_t*>(textureBuffer.data());
const uint8_t** pdata = const_cast<const uint8_t**>(&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("<filter-blur> 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("<filter-blur> Loading box-blur effect failed with error(s): %s", loadError);
P_LOG_ERROR("<filter-blur> Loading box-blur effect failed with error(s): %s", loadError);
bfree(loadError);
} else if (!g_boxBlurEffect) {
PLOG_ERROR("<filter-blur> Loading box-blur effect failed with unspecified error.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur> Loading gaussian blur effect failed with error(s): %s", loadError);
P_LOG_ERROR("<filter-blur> Loading gaussian blur effect failed with error(s): %s", loadError);
bfree(loadError);
} else if (!effect) {
PLOG_ERROR("<filter-blur> Loading gaussian blur effect failed with unspecified error.");
P_LOG_ERROR("<filter-blur> Loading gaussian blur effect failed with unspecified error.");
} else {
g_gaussianBlur.effect = effect;
g_gaussianBlur.kernels.resize(g_maxKernelSize);
std::vector<float_t> 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<uint8_t*>(databuf.data());
const uint8_t** pdata = const_cast<const uint8_t**>(&data);
gs_texture_t* tex = gs_texture_create(n, 1, gs_color_format::GS_R32F, 1, pdata, 0);
if (!tex) {
PLOG_ERROR("<filter-blur> 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("<filter-blur> Loading bilateral blur effect failed with error(s): %s", loadError);
P_LOG_ERROR("<filter-blur> Loading bilateral blur effect failed with error(s): %s", loadError);
bfree(loadError);
} else if (!effect) {
PLOG_ERROR("<filter-blur> Loading bilateral blur effect failed with unspecified error.");
P_LOG_ERROR("<filter-blur> Loading bilateral blur effect failed with unspecified error.");
} else {
g_bilateralBlur.effect = effect;
g_bilateralBlur.kernels.resize(g_maxKernelSize);
std::vector<float_t> 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<uint8_t*>(databuf.data());
const uint8_t** pdata = const_cast<const uint8_t**>(&data);
gs_texture_t* tex = gs_texture_create(n, 1, gs_color_format::GS_R32F, 1, pdata, 0);
if (!tex) {
PLOG_ERROR("<filter-blur> 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("<filter-blur> Loading color conversion effect failed with error(s): %s", loadError);
P_LOG_ERROR("<filter-blur> Loading color conversion effect failed with error(s): %s", loadError);
bfree(loadError);
} else if (!g_colorConversionEffect) {
PLOG_ERROR("<filter-blur> Loading color conversion effect failed with unspecified error.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur> Failed to set up base texture.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur> Unable to render source.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur> Failed to get source texture.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur> Failed to set up base texture.");
P_LOG_ERROR("<filter-blur> 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("<filter-blur:Final> Failed to set image param.");
P_LOG_ERROR("<filter-blur:Final> 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("<filter-blur> Failed to get source texture.");
P_LOG_ERROR("<filter-blur> 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<const char*, gs_texrender_t*, float, float> 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("<filter-blur:%s> 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("<filter-blur:%s> 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("<filter-blur:Final> Failed to set image param.");
P_LOG_ERROR("<filter-blur:Final> 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("<filter-blur:Horizontal> 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("<filter-blur:Horizontal> 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("<filter-blur:Horizontal> 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("<filter-blur:Vertical> 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("<filter-blur:Vertical> 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("<filter-blur:Vertical> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> 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("<filter-blur> Failed to set texel param.");
if (m_type != Type::Bilateral)
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set widthHalf param.");
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set width param.");
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set image param.");
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set bilateralSmoothing param.");
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set bilateralSmoothing param.");
return false;
} else {
P_LOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set bilateralSmoothing param.");
return false;
} else {
PLOG_DEBUG("<filter-blur> 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("<filter-blur> Failed to set bilateralSmoothing param.");
return false;
} else {
P_LOG_DEBUG("<filter-blur> 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;
}

View File

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