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; // Provided by OBS
uniform texture2d image; uniform float4x4 ViewProj <
uniform texture2d displacementMap; bool visible = false;
uniform float2 texelScale; >;
uniform float2 displacementScale;
sampler_state textureSampler { uniform texture2d image <
Filter = Linear; bool visible = false;
AddressU = Wrap; >;
AddressV = Wrap;
}; // Parameters
sampler_state dispTextureSampler { uniform float2 image_size <
Filter = Linear; bool visible = false;
AddressU = Clamp; >;
AddressV = Clamp;
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; float4 pos : POSITION;
float2 uv : TEXCOORD0; float2 uv : TEXCOORD0;
}; };
struct VertDataOut { // Functions
float4 pos : POSITION; FunctionData vertex_shader(FunctionData v) {
float2 uv : TEXCOORD0; v.pos = mul(float4(v.pos.xyz, 1.0), ViewProj);
return v;
}; };
VertDataOut VSDefault(VertDataIn v_in) float4 pixel_shader(FunctionData v) : TARGET {
{ float4 v_normal = normal.Sample(smp_linear_wrap, v.uv);
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 PSDisplace(VertDataOut v_in) : TARGET float2 offset = v_normal.rg;
{ offset -= float2(.5, .5);
float2 disp = displacementMap.Sample(dispTextureSampler, v_in.uv).rg - float2(.5, .5); offset *= 255.0;
offset = floor(abs(offset)) * sign(offset);
offset /= 127.0;
// Method 1: Only Math offset *= lerp(float2(1.0, 1.0), image_inverse_size, scale_type);
disp = (floor(abs(disp * 255.0)) / 255.0) * sign(disp); offset *= scale;
float2 uv = v_in.uv + (disp * texelScale * displacementScale); return image.Sample(smp_linear_clamp, v.uv + offset);
};
return image.Sample(textureSampler, uv);
}
// Techniques
technique Draw technique Draw
{ {
pass pass
{ {
vertex_shader = VSDefault(v_in); vertex_shader = vertex_shader(v);
pixel_shader = PSDisplace(v_in); pixel_shader = pixel_shader(v);
} }
} }

View file

@ -165,9 +165,10 @@ Filter.ColorGrade.Correction.Contrast="Contrast"
# Filter - Displacement # Filter - Displacement
Filter.Displacement="Displacement Mapping" Filter.Displacement="Displacement Mapping"
Filter.Displacement.File="File" 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="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 - Dynamic Mask
Filter.DynamicMask="Dynamic Mask" Filter.DynamicMask="Dynamic Mask"

View file

@ -24,276 +24,77 @@
#define ST "Filter.Displacement" #define ST "Filter.Displacement"
#define ST_FILE "Filter.Displacement.File" #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" #define ST_SCALE "Filter.Displacement.Scale"
#define ST_SCALE_TYPE "Filter.Displacment.Scale.Type"
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 (...) {
}
}
}
filter::displacement::displacement_instance::displacement_instance(obs_data_t* data, obs_source_t* context) filter::displacement::displacement_instance::displacement_instance(obs_data_t* data, obs_source_t* context)
: _self(context), _timer(), _effect(), _distance(), _displacement_scale(), _file_create_time(), : obs::source_instance(data, context)
_file_modified_time(), _file_size()
{ {
char* effectFile = obs_module_file("effects/displace.effect"); std::string effect = "";
if (effectFile) { {
try { char* buf = obs_module_file("effects/displace.effect");
_effect = gs::effect::create(effectFile); effect = buf;
} catch (...) { bfree(buf);
P_LOG_ERROR("<Displacement Filter:%s> Failed to load displacement effect.", obs_source_get_name(_self));
}
bfree(effectFile);
} }
update(data); _effect = gs::effect::create(effect);
} }
filter::displacement::displacement_instance::~displacement_instance() filter::displacement::displacement_instance::~displacement_instance()
{ {
_effect.reset(); _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)); update(settings);
_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)));
} }
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() {} std::string new_file = obs_data_get_string(settings, ST_FILE);
if (new_file != _texture_file) {
void filter::displacement::displacement_instance::show() {} try {
_texture = std::make_shared<gs::texture>(new_file);
void filter::displacement::displacement_instance::hide() {} _texture_file = new_file;
} catch (...) {
void filter::displacement::displacement_instance::video_tick(float time) _texture.reset();
{ }
_timer += time;
if (_timer >= 1.0f) {
_timer -= 1.0f;
validate_file_texture(_file_name);
} }
} }
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*) void filter::displacement::displacement_instance::video_render(gs_effect_t*)
{ {
obs_source_t* parent = obs_filter_get_parent(_self); if (!_texture) { // No displacement map, so just skip us for now.
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) {
obs_source_skip_video_filter(_self); obs_source_skip_video_filter(_self);
return; return;
} }
@ -302,22 +103,82 @@ void filter::displacement::displacement_instance::video_render(gs_effect_t*)
obs_source_skip_video_filter(_self); obs_source_skip_video_filter(_self);
return; 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")) { obs_source_process_filter_end(_self, _effect->get_object(), _width, _height);
_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);
} }
std::string filter::displacement::displacement_instance::get_file() 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 <memory>
#include <string> #include <string>
#include "obs/gs/gs-effect.hpp" #include "obs/gs/gs-effect.hpp"
#include "obs/obs-source-factory.hpp"
#include "plugin.hpp" #include "plugin.hpp"
// OBS // OBS
@ -36,52 +37,61 @@
namespace filter { namespace filter {
namespace displacement { namespace displacement {
class displacement_factory { class displacement_instance : public obs::source_instance {
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
std::shared_ptr<gs::effect> _effect; std::shared_ptr<gs::effect> _effect;
float_t _distance;
vec2 _displacement_scale;
// Displacement Map // Displacement Map
std::string _file_name; std::shared_ptr<gs::texture> _texture;
std::shared_ptr<gs::texture> _file_texture; std::string _texture_file;
time_t _file_create_time; float_t _scale[2];
time_t _file_modified_time; float_t _scale_type;
size_t _file_size;
void validate_file_texture(std::string file); // Cache
uint32_t _width;
uint32_t _height;
public: public:
displacement_instance(obs_data_t*, obs_source_t*); displacement_instance(obs_data_t*, obs_source_t*);
~displacement_instance(); virtual ~displacement_instance();
void update(obs_data_t*); virtual void load(obs_data_t* settings) override;
uint32_t get_width(); virtual void update(obs_data_t* settings) override;
uint32_t get_height();
void activate(); virtual void video_tick(float_t) override;
void deactivate(); virtual void video_render(gs_effect_t*) override;
void show();
void hide();
void video_tick(float);
void video_render(gs_effect_t*);
std::string get_file(); 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 displacement
} // namespace filter } // namespace filter