filter-displacement: Refactor onto obs::source_factory

This drastically improves stability and prevents all exceptions from leaking into libobs C code, which prevents crashes and unexpected freezes from exception handlers further down the stack.

Additionally minor work was done to further improve the quality and user experience for the filter.
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2019-11-17 14:09:59 +01:00
parent aa170c7c54
commit df8ebd94ea
4 changed files with 236 additions and 328 deletions

View file

@ -1,55 +1,91 @@
uniform float4x4 ViewProj;
uniform texture2d image;
uniform texture2d displacementMap;
uniform float2 texelScale;
uniform float2 displacementScale;
// Provided by OBS
uniform float4x4 ViewProj <
bool visible = false;
>;
sampler_state textureSampler {
Filter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
sampler_state dispTextureSampler {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
uniform texture2d image <
bool visible = false;
>;
// Parameters
uniform float2 image_size <
bool visible = false;
>;
uniform float2 image_inverse_size <
bool visible = false;
>;
uniform texture2d normal <
bool visible = true;
string name = "Normal Map";
string description = "A normal map that is used for displacing the texture sample locations.";
>;
uniform float2 scale <
bool visible = true;
string mode = "slider";
float2 minimum = {0.0, 0.0};
float2 maximum = {100.0, 100.0};
float2 step = {0.01, 0.01};
> = {0.0, 0.0};
uniform float scale_type <
bool visible = true;
string mode = "slider";
string name = "Scale Mode";
string description = "A value of 0.0 is in Texel Space, while a value of 100.0 is in Pixel Space.";
float2 minimum = {0.0, 0.0};
float2 maximum = {100.0, 100.0};
float2 step = {0.01, 0.01};
> = 0.0;
// Samplers
sampler_state smp_linear_wrap {
Filter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
struct VertDataIn {
sampler_state smp_linear_clamp {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
// Structs
struct FunctionData {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float2 uv : TEXCOORD0;
};
struct VertDataOut {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
// Functions
FunctionData vertex_shader(FunctionData v) {
v.pos = mul(float4(v.pos.xyz, 1.0), ViewProj);
return v;
};
VertDataOut VSDefault(VertDataIn v_in)
{
VertDataOut vert_out;
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
vert_out.uv = v_in.uv;
return vert_out;
}
float4 pixel_shader(FunctionData v) : TARGET {
float4 v_normal = normal.Sample(smp_linear_wrap, v.uv);
float4 PSDisplace(VertDataOut v_in) : TARGET
{
float2 disp = displacementMap.Sample(dispTextureSampler, v_in.uv).rg - float2(.5, .5);
float2 offset = v_normal.rg;
offset -= float2(.5, .5);
offset *= 255.0;
offset = floor(abs(offset)) * sign(offset);
offset /= 127.0;
// Method 1: Only Math
disp = (floor(abs(disp * 255.0)) / 255.0) * sign(disp);
offset *= lerp(float2(1.0, 1.0), image_inverse_size, scale_type);
offset *= scale;
float2 uv = v_in.uv + (disp * texelScale * displacementScale);
return image.Sample(textureSampler, uv);
}
return image.Sample(smp_linear_clamp, v.uv + offset);
};
// Techniques
technique Draw
{
pass
{
vertex_shader = VSDefault(v_in);
pixel_shader = PSDisplace(v_in);
vertex_shader = vertex_shader(v);
pixel_shader = pixel_shader(v);
}
}

View file

@ -165,9 +165,10 @@ Filter.ColorGrade.Correction.Contrast="Contrast"
# Filter - Displacement
Filter.Displacement="Displacement Mapping"
Filter.Displacement.File="File"
Filter.Displacement.File.Types="Images (*.png *.jpeg *.jpg *.bmp *.tga);;All Files (*)"
Filter.Displacement.Ratio="Ratio"
Filter.Displacement.Scale="Scale"
Filter.Displacement.Scale.Description="Scale of the displacement, either in pixels (Scale Type = 100.0) or in UVs (Scale Type = 0.0)."
Filter.Displacement.Scale.Type="Scaling Type"
Filter.Displacement.Scale.Type.Description="Type of the displacement scale, with\nvalues closer to 0.00 being UV space and\nvalues closer to 100.00 being Pixel space."
# Filter - Dynamic Mask
Filter.DynamicMask="Dynamic Mask"

View file

@ -24,276 +24,77 @@
#define ST "Filter.Displacement"
#define ST_FILE "Filter.Displacement.File"
#define ST_FILE_TYPES "Filter.Displacement.File.Types"
#define ST_RATIO "Filter.Displacement.Ratio"
#define ST_SCALE "Filter.Displacement.Scale"
static const char* get_name(void*) noexcept try {
return D_TRANSLATE(ST);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
return "";
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
return "";
}
static void* create(obs_data_t* data, obs_source_t* source) noexcept try {
return new filter::displacement::displacement_instance(data, source);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
return nullptr;
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
return nullptr;
}
static void destroy(void* ptr) noexcept try {
delete reinterpret_cast<filter::displacement::displacement_instance*>(ptr);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void get_defaults(obs_data_t* data) noexcept try {
char* disp = obs_module_file("filter-displacement/neutral.png");
obs_data_set_default_string(data, ST_FILE, disp);
obs_data_set_default_double(data, ST_RATIO, 0);
obs_data_set_default_double(data, ST_SCALE, 0);
bfree(disp);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static obs_properties_t* get_properties(void* ptr) noexcept try {
obs_properties_t* pr = obs_properties_create();
std::string path = "";
if (ptr)
path = reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->get_file();
obs_properties_add_path(pr, ST_FILE, D_TRANSLATE(ST_FILE), obs_path_type::OBS_PATH_FILE, D_TRANSLATE(ST_FILE_TYPES),
path.c_str());
obs_properties_add_float_slider(pr, ST_RATIO, D_TRANSLATE(ST_RATIO), 0, 1, 0.01);
obs_properties_add_float_slider(pr, ST_SCALE, D_TRANSLATE(ST_SCALE), -1000, 1000, 0.01);
return pr;
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
return nullptr;
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
return nullptr;
}
static void update(void* ptr, obs_data_t* data) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->update(data);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void activate(void* ptr) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->activate();
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void deactivate(void* ptr) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->deactivate();
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void show(void* ptr) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->show();
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void hide(void* ptr) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->hide();
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void video_tick(void* ptr, float time) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->video_tick(time);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static void video_render(void* ptr, gs_effect_t* effect) noexcept try {
reinterpret_cast<filter::displacement::displacement_instance*>(ptr)->video_render(effect);
} catch (const std::exception& ex) {
P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
} catch (...) {
P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
}
static std::shared_ptr<filter::displacement::displacement_factory> factory_instance = nullptr;
void filter::displacement::displacement_factory::initialize()
{
factory_instance = std::make_shared<filter::displacement::displacement_factory>();
}
void filter::displacement::displacement_factory::finalize()
{
factory_instance.reset();
}
std::shared_ptr<filter::displacement::displacement_factory> filter::displacement::displacement_factory::get()
{
return factory_instance;
}
filter::displacement::displacement_factory::displacement_factory()
{
memset(&_source_info, 0, sizeof(obs_source_info));
_source_info.id = "obs-stream-effects-filter-displacement";
_source_info.type = OBS_SOURCE_TYPE_FILTER;
_source_info.output_flags = OBS_SOURCE_VIDEO;
_source_info.get_name = get_name;
_source_info.get_defaults = get_defaults;
_source_info.get_properties = get_properties;
_source_info.create = create;
_source_info.destroy = destroy;
_source_info.update = update;
_source_info.activate = activate;
_source_info.deactivate = deactivate;
_source_info.show = show;
_source_info.hide = hide;
_source_info.video_tick = video_tick;
_source_info.video_render = video_render;
obs_register_source(&_source_info);
}
filter::displacement::displacement_factory::~displacement_factory() {}
void filter::displacement::displacement_instance::validate_file_texture(std::string file)
{
bool do_update = false;
// Don't allow empty file names.
if (file.length() == 0) {
return;
}
// File name different
if (file != _file_name) {
do_update = true;
_file_name = file;
}
// Timestamp verification
struct stat stats;
if (os_stat(_file_name.c_str(), &stats) != 0) {
do_update = do_update || (stats.st_ctime != _file_create_time);
do_update = do_update || (stats.st_mtime != _file_modified_time);
do_update = do_update || (static_cast<size_t>(stats.st_size) != _file_size);
_file_create_time = stats.st_ctime;
_file_modified_time = stats.st_mtime;
_file_size = static_cast<size_t>(stats.st_size);
}
do_update = !_file_texture || do_update;
if (do_update) {
try {
_file_texture = std::make_shared<gs::texture>(_file_name);
} catch (...) {
}
}
}
#define ST_SCALE_TYPE "Filter.Displacment.Scale.Type"
filter::displacement::displacement_instance::displacement_instance(obs_data_t* data, obs_source_t* context)
: _self(context), _timer(), _effect(), _distance(), _displacement_scale(), _file_create_time(),
_file_modified_time(), _file_size()
: obs::source_instance(data, context)
{
char* effectFile = obs_module_file("effects/displace.effect");
if (effectFile) {
try {
_effect = gs::effect::create(effectFile);
} catch (...) {
P_LOG_ERROR("<Displacement Filter:%s> Failed to load displacement effect.", obs_source_get_name(_self));
}
bfree(effectFile);
std::string effect = "";
{
char* buf = obs_module_file("effects/displace.effect");
effect = buf;
bfree(buf);
}
update(data);
_effect = gs::effect::create(effect);
}
filter::displacement::displacement_instance::~displacement_instance()
{
_effect.reset();
_file_texture.reset();
_texture.reset();
}
void filter::displacement::displacement_instance::update(obs_data_t* data)
void filter::displacement::displacement_instance::load(obs_data_t* settings)
{
validate_file_texture(obs_data_get_string(data, ST_FILE));
_distance = float_t(obs_data_get_double(data, ST_RATIO));
vec2_set(&_displacement_scale, float_t(obs_data_get_double(data, ST_SCALE)),
float_t(obs_data_get_double(data, ST_SCALE)));
update(settings);
}
uint32_t filter::displacement::displacement_instance::get_width()
inline void migrate_settings(obs_data_t* settings)
{
return 0;
uint64_t version = static_cast<uint64_t>(obs_data_get_int(settings, S_VERSION));
switch (version & STREAMEFFECTS_MASK_COMPAT) {
case 0:
obs_data_set_double(settings, ST_SCALE, obs_data_get_double(settings, "Filter.Displacement.Scale") * 0.5);
obs_data_set_double(settings, ST_SCALE_TYPE,
obs_data_get_double(settings, "Filter.Displacement.Ratio") * 100.0);
obs_data_unset_user_value(settings, "Filter.Displacement.Ratio");
case STREAMEFFECTS_MAKE_VERSION(0, 8, 0, 0):
break;
}
obs_data_set_int(settings, S_VERSION, STREAMEFFECTS_VERSION);
}
uint32_t filter::displacement::displacement_instance::get_height()
void filter::displacement::displacement_instance::update(obs_data_t* settings)
{
return 0;
}
migrate_settings(settings);
void filter::displacement::displacement_instance::activate() {}
_scale[0] = _scale[1] = static_cast<float_t>(obs_data_get_double(settings, ST_SCALE));
_scale_type = static_cast<float_t>(obs_data_get_double(settings, ST_SCALE_TYPE) / 100.0);
void filter::displacement::displacement_instance::deactivate() {}
void filter::displacement::displacement_instance::show() {}
void filter::displacement::displacement_instance::hide() {}
void filter::displacement::displacement_instance::video_tick(float time)
{
_timer += time;
if (_timer >= 1.0f) {
_timer -= 1.0f;
validate_file_texture(_file_name);
std::string new_file = obs_data_get_string(settings, ST_FILE);
if (new_file != _texture_file) {
try {
_texture = std::make_shared<gs::texture>(new_file);
_texture_file = new_file;
} catch (...) {
_texture.reset();
}
}
}
static float interp(float a, float b, float v)
void filter::displacement::displacement_instance::video_tick(float_t)
{
return (a * (1.0f - v)) + (b * v);
_width = obs_source_get_base_width(_self);
_height = obs_source_get_base_height(_self);
}
void filter::displacement::displacement_instance::video_render(gs_effect_t*)
{
obs_source_t* parent = obs_filter_get_parent(_self);
obs_source_t* target = obs_filter_get_target(_self);
uint32_t baseW = obs_source_get_base_width(target), baseH = obs_source_get_base_height(target);
// Skip rendering if our target, parent or context is not valid.
if (!parent || !target || !baseW || !baseH || !_file_texture) {
if (!_texture) { // No displacement map, so just skip us for now.
obs_source_skip_video_filter(_self);
return;
}
@ -302,22 +103,82 @@ void filter::displacement::displacement_instance::video_render(gs_effect_t*)
obs_source_skip_video_filter(_self);
return;
}
_effect->get_parameter("image_size")->set_float2(static_cast<float_t>(_width), static_cast<float_t>(_height));
_effect->get_parameter("image_inverse_size")
->set_float2(static_cast<float_t>(1.0 / _width), static_cast<float_t>(1.0 / _height));
_effect->get_parameter("normal")->set_texture(_texture->get_object());
_effect->get_parameter("scale")->set_float2(_scale[0], _scale[1]);
_effect->get_parameter("scale_type")->set_float(_scale_type);
if (_effect->has_parameter("texelScale")) {
_effect->get_parameter("texelScale")
->set_float2(interp((1.0f / baseW), 1.0f, _distance), interp((1.0f / baseH), 1.0f, _distance));
}
if (_effect->has_parameter("displacementScale")) {
_effect->get_parameter("displacementScale")->set_float2(_displacement_scale);
}
if (_effect->has_parameter("displacementMap")) {
_effect->get_parameter("displacementMap")->set_texture(_file_texture);
}
obs_source_process_filter_end(_self, _effect->get_object(), baseW, baseH);
obs_source_process_filter_end(_self, _effect->get_object(), _width, _height);
}
std::string filter::displacement::displacement_instance::get_file()
{
return _file_name;
return _texture_file;
}
std::shared_ptr<filter::displacement::displacement_factory>
filter::displacement::displacement_factory::factory_instance = nullptr;
filter::displacement::displacement_factory::displacement_factory()
{
_info.id = "obs-stream-effects-filter-displacement";
_info.type = OBS_SOURCE_TYPE_FILTER;
_info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW;
_info.get_width = _info.get_height = nullptr;
obs_register_source(&_info);
}
filter::displacement::displacement_factory::~displacement_factory() {}
const char* filter::displacement::displacement_factory::get_name()
{
return D_TRANSLATE(ST);
}
void filter::displacement::displacement_factory::get_defaults2(obs_data_t* data)
{
{
char* disp = obs_module_file("examples/normal-maps/neutral.png");
obs_data_set_default_string(data, ST_FILE, disp);
bfree(disp);
}
obs_data_set_default_double(data, ST_SCALE, 0.0);
obs_data_set_default_double(data, ST_SCALE_TYPE, 0.0);
}
obs_properties_t*
filter::displacement::displacement_factory::get_properties2(filter::displacement::displacement_instance* data)
{
obs_properties_t* pr = obs_properties_create();
std::string path = "";
if (data) {
path = data->get_file();
} else {
char* buf = obs_module_file("examples/normal-maps/neutral.png");
path = buf;
bfree(buf);
}
{
auto p = obs_properties_add_path(pr, ST_FILE, D_TRANSLATE(ST_FILE), obs_path_type::OBS_PATH_FILE,
D_TRANSLATE(S_FILEFILTERS_TEXTURE), path.c_str());
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_FILE)));
}
{
auto p = obs_properties_add_float(pr, ST_SCALE, D_TRANSLATE(ST_SCALE), -10000000.0, 10000000.0, 0.01);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SCALE)));
}
{
auto p = obs_properties_add_float_slider(pr, ST_SCALE_TYPE, D_TRANSLATE(ST_SCALE_TYPE), 0.0, 100.0, 0.01);
obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SCALE_TYPE)));
}
return pr;
}

View file

@ -21,6 +21,7 @@
#include <memory>
#include <string>
#include "obs/gs/gs-effect.hpp"
#include "obs/obs-source-factory.hpp"
#include "plugin.hpp"
// OBS
@ -36,52 +37,61 @@
namespace filter {
namespace displacement {
class displacement_factory {
obs_source_info _source_info;
public: // Singleton
static void initialize();
static void finalize();
static std::shared_ptr<displacement_factory> get();
public:
displacement_factory();
~displacement_factory();
};
class displacement_instance {
obs_source_t* _self;
float_t _timer;
// Rendering
class displacement_instance : public obs::source_instance {
std::shared_ptr<gs::effect> _effect;
float_t _distance;
vec2 _displacement_scale;
// Displacement Map
std::string _file_name;
std::shared_ptr<gs::texture> _file_texture;
time_t _file_create_time;
time_t _file_modified_time;
size_t _file_size;
std::shared_ptr<gs::texture> _texture;
std::string _texture_file;
float_t _scale[2];
float_t _scale_type;
void validate_file_texture(std::string file);
// Cache
uint32_t _width;
uint32_t _height;
public:
displacement_instance(obs_data_t*, obs_source_t*);
~displacement_instance();
virtual ~displacement_instance();
void update(obs_data_t*);
uint32_t get_width();
uint32_t get_height();
void activate();
void deactivate();
void show();
void hide();
void video_tick(float);
void video_render(gs_effect_t*);
virtual void load(obs_data_t* settings) override;
virtual void update(obs_data_t* settings) override;
virtual void video_tick(float_t) override;
virtual void video_render(gs_effect_t*) override;
std::string get_file();
};
class displacement_factory : public obs::source_factory<filter::displacement::displacement_factory,
filter::displacement::displacement_instance> {
static std::shared_ptr<filter::displacement::displacement_factory> factory_instance;
public: // Singleton
static void initialize()
{
factory_instance = std::make_shared<filter::displacement::displacement_factory>();
}
static void finalize()
{
factory_instance.reset();
}
static std::shared_ptr<displacement_factory> get()
{
return factory_instance;
}
public:
displacement_factory();
virtual ~displacement_factory();
virtual const char* get_name() override;
virtual void get_defaults2(obs_data_t* data) override;
virtual obs_properties_t* get_properties2(filter::displacement::displacement_instance* data) override;
};
} // namespace displacement
} // namespace filter