diff --git a/data/effects/displace.effect b/data/effects/displace.effect index 05b4801..9f30ed1 100644 --- a/data/effects/displace.effect +++ b/data/effects/displace.effect @@ -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); } } diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 9940cae..0b5c5a5 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -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" diff --git a/source/filters/filter-displacement.cpp b/source/filters/filter-displacement.cpp index 640fb92..587708f 100644 --- a/source/filters/filter-displacement.cpp +++ b/source/filters/filter-displacement.cpp @@ -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(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(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(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(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(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(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(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(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(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 factory_instance = nullptr; - -void filter::displacement::displacement_factory::initialize() -{ - factory_instance = std::make_shared(); -} - -void filter::displacement::displacement_factory::finalize() -{ - factory_instance.reset(); -} - -std::shared_ptr 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(stats.st_size) != _file_size); - _file_create_time = stats.st_ctime; - _file_modified_time = stats.st_mtime; - _file_size = static_cast(stats.st_size); - } - - do_update = !_file_texture || do_update; - - if (do_update) { - try { - _file_texture = std::make_shared(_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(" 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(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(obs_data_get_double(settings, ST_SCALE)); + _scale_type = static_cast(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(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(_width), static_cast(_height)); + _effect->get_parameter("image_inverse_size") + ->set_float2(static_cast(1.0 / _width), static_cast(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::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; } diff --git a/source/filters/filter-displacement.hpp b/source/filters/filter-displacement.hpp index 2a09b25..8fe8e4e 100644 --- a/source/filters/filter-displacement.hpp +++ b/source/filters/filter-displacement.hpp @@ -21,6 +21,7 @@ #include #include #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 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 _effect; - float_t _distance; - vec2 _displacement_scale; // Displacement Map - std::string _file_name; - std::shared_ptr _file_texture; - time_t _file_create_time; - time_t _file_modified_time; - size_t _file_size; + std::shared_ptr _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 { + static std::shared_ptr factory_instance; + + public: // Singleton + static void initialize() + { + factory_instance = std::make_shared(); + } + + static void finalize() + { + factory_instance.reset(); + } + + static std::shared_ptr 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