// Modern effects for a modern Streamer // Copyright (C) 2017 Michael Fabian Dirks // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA #include "gfx-effect-source.hpp" #include #include #include #include #include #include #include "obs/gs/gs-helper.hpp" #include "obs/obs-source-tracker.hpp" #include "strings.hpp" #define ST_FILE S_SHADER_FILE #define ST_TECHNIQUE S_SHADER_TECHNIQUE #define ST_TEXTURE_TYPE "Shader.Texture.Type" #define ST_TEXTURE_FILE S_FILETYPE_IMAGE #define ST_TEXTURE_SOURCE S_SOURCETYPE_SOURCE #define UNIQUE_PREFIX "HelloThisIsPatrick." static std::vector static_parameters{ "ViewProj", "Time", "Random", }; gfx::effect_source::parameter::parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : _parent(parent), _effect(effect), _param(param), _description(""), _formulae(""), _visible(true) { if (!effect) throw std::invalid_argument("effect"); if (!effect) throw std::invalid_argument("param"); if (!effect->has_parameter(param->get_name(), param->get_type())) throw std::invalid_argument("param"); _name = UNIQUE_PREFIX + _param->get_name(); _visible_name = _name; if (param->has_annotation("name", gs::effect_parameter::type::String)) { param->get_annotation("name")->get_default_string(_visible_name); } if (param->has_annotation("description", gs::effect_parameter::type::String)) { param->get_annotation("description")->get_default_string(_description); } if (param->has_annotation("formulae", gs::effect_parameter::type::String)) { param->get_annotation("formulae")->get_default_string(_formulae); } if (param->has_annotation("visible", gs::effect_parameter::type::Boolean)) { param->get_annotation("visible")->get_default_bool(_visible); } else { _visible = true; } } gfx::effect_source::parameter::~parameter() {} void gfx::effect_source::parameter::defaults(obs_properties_t* props, obs_data_t* data) {} void gfx::effect_source::parameter::properties(obs_properties_t* props) {} void gfx::effect_source::parameter::remove_properties(obs_properties_t* props) {} void gfx::effect_source::parameter::update(obs_data_t* data) {} void gfx::effect_source::parameter::tick(float_t time) {} void gfx::effect_source::parameter::prepare() {} void gfx::effect_source::parameter::assign() {} std::shared_ptr gfx::effect_source::parameter::get_param() { return _param; } std::shared_ptr gfx::effect_source::parameter::create(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) { if (!effect) throw std::invalid_argument("effect"); if (!effect) throw std::invalid_argument("param"); if (!effect->has_parameter(param->get_name(), param->get_type())) throw std::invalid_argument("param"); switch (param->get_type()) { case gs::effect_parameter::type::Boolean: return std::make_shared(parent, effect, param); case gs::effect_parameter::type::Float: case gs::effect_parameter::type::Float2: case gs::effect_parameter::type::Float3: case gs::effect_parameter::type::Float4: case gs::effect_parameter::type::Integer: case gs::effect_parameter::type::Integer2: case gs::effect_parameter::type::Integer3: case gs::effect_parameter::type::Integer4: return std::make_shared(parent, effect, param); case gs::effect_parameter::type::Matrix: return std::make_shared(parent, effect, param); case gs::effect_parameter::type::Texture: return std::make_shared(parent, effect, param); } return nullptr; } gfx::effect_source::bool_parameter::bool_parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : parameter(parent, effect, param) { if (param->get_type() != gs::effect_parameter::type::Boolean) throw std::bad_cast(); param->get_default_bool(_value); } void gfx::effect_source::bool_parameter::defaults(obs_properties_t* props, obs_data_t* data) { obs_data_set_default_bool(data, _name.c_str(), _value); obs_data_set_bool(data, _name.c_str(), _value); } void gfx::effect_source::bool_parameter::properties(obs_properties_t* props) { auto p = obs_properties_add_bool(props, _name.c_str(), _visible_name.c_str()); obs_property_set_long_description(p, _description.c_str()); obs_property_set_visible(p, _visible); } void gfx::effect_source::bool_parameter::remove_properties(obs_properties_t* props) { obs_properties_remove_by_name(props, _name.c_str()); } void gfx::effect_source::bool_parameter::update(obs_data_t* data) { _value = obs_data_get_bool(data, _name.c_str()); } void gfx::effect_source::bool_parameter::tick(float_t time) {} void gfx::effect_source::bool_parameter::prepare() {} void gfx::effect_source::bool_parameter::assign() { _param->set_bool(_value); } gfx::effect_source::value_parameter::value_parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : parameter(parent, effect, param) { std::shared_ptr min = param->get_annotation("minimum"); std::shared_ptr max = param->get_annotation("maximum"); std::shared_ptr stp = param->get_annotation("step"); bool is_int = false; switch (param->get_type()) { case gs::effect_parameter::type::Float: param->get_default_float(_value.f[0]); if (min) min->get_default_float(_minimum.f[0]); if (max) max->get_default_float(_maximum.f[0]); if (stp) stp->get_default_float(_step.f[0]); break; case gs::effect_parameter::type::Float2: param->get_default_float2(_value.f[0], _value.f[1]); if (min) min->get_default_float2(_minimum.f[0], _minimum.f[1]); if (max) max->get_default_float2(_maximum.f[0], _maximum.f[1]); if (stp) stp->get_default_float2(_step.f[0], _step.f[1]); break; case gs::effect_parameter::type::Float3: param->get_default_float3(_value.f[0], _value.f[1], _value.f[2]); if (min) min->get_default_float3(_minimum.f[0], _minimum.f[1], _minimum.f[2]); if (max) max->get_default_float3(_maximum.f[0], _maximum.f[1], _maximum.f[2]); if (stp) stp->get_default_float3(_step.f[0], _step.f[1], _step.f[2]); break; case gs::effect_parameter::type::Float4: param->get_default_float4(_value.f[0], _value.f[1], _value.f[2], _value.f[3]); if (min) min->get_default_float4(_minimum.f[0], _minimum.f[1], _minimum.f[2], _minimum.f[3]); if (max) max->get_default_float4(_maximum.f[0], _maximum.f[1], _maximum.f[2], _maximum.f[3]); if (stp) stp->get_default_float4(_step.f[0], _step.f[1], _step.f[2], _step.f[3]); break; case gs::effect_parameter::type::Integer: param->get_default_int(_value.i[0]); if (min) min->get_default_int(_minimum.i[0]); if (max) max->get_default_int(_maximum.i[0]); if (stp) stp->get_default_int(_step.i[0]); is_int = true; break; case gs::effect_parameter::type::Integer2: param->get_default_int2(_value.i[0], _value.i[1]); if (min) min->get_default_int2(_minimum.i[0], _minimum.i[1]); if (max) max->get_default_int2(_maximum.i[0], _maximum.i[1]); if (stp) stp->get_default_int2(_step.i[0], _step.i[1]); is_int = true; break; case gs::effect_parameter::type::Integer3: param->get_default_int3(_value.i[0], _value.i[1], _value.i[2]); if (min) min->get_default_int3(_minimum.i[0], _minimum.i[1], _minimum.i[2]); if (max) max->get_default_int3(_maximum.i[0], _maximum.i[1], _maximum.i[2]); if (stp) stp->get_default_int3(_step.i[0], _step.i[1], _step.i[2]); is_int = true; break; case gs::effect_parameter::type::Integer4: param->get_default_int4(_value.i[0], _value.i[1], _value.i[2], _value.i[3]); if (min) min->get_default_int4(_minimum.i[0], _minimum.i[1], _minimum.i[2], _minimum.i[3]); if (max) max->get_default_int4(_maximum.i[0], _maximum.i[1], _maximum.i[2], _maximum.i[3]); if (stp) stp->get_default_int4(_step.i[0], _step.i[1], _step.i[2], _step.i[3]); is_int = true; break; default: throw std::bad_cast(); } if (!min) { if (is_int) { _minimum.i[0] = _minimum.i[1] = _minimum.i[2] = _minimum.i[3] = std::numeric_limits::min(); } else { _minimum.f[0] = _minimum.f[1] = _minimum.f[2] = _minimum.f[3] = -167772.16f; } } if (!max) { if (is_int) { _maximum.i[0] = _maximum.i[1] = _maximum.i[2] = _maximum.i[3] = std::numeric_limits::max(); } else { _maximum.f[0] = _maximum.f[1] = _maximum.f[2] = _maximum.f[3] = +167772.16f; } } if (!stp) { if (is_int) { _step.i[0] = _step.i[1] = _step.i[2] = _step.i[3] = 1; } else { _step.f[0] = _step.f[1] = _step.f[2] = _step.f[3] = 0.01f; } } std::shared_ptr mode = param->get_annotation("mode"); if (mode && (mode->get_type() == gs::effect_parameter::type::String)) { std::string mode_str = mode->get_default_string(); if (strcmpi(mode_str.c_str(), "slider") == 0) { _mode = value_mode::SLIDER; } else { _mode = value_mode::INPUT; } } for (size_t idx = 0; idx < 4; idx++) { std::stringstream name_sstr; std::stringstream ui_sstr; name_sstr << _name << '[' << idx << ']'; ui_sstr << _visible_name << '[' << idx << ']'; _cache.name[idx] = name_sstr.str(); _cache.visible_name[idx] = ui_sstr.str(); } } void gfx::effect_source::value_parameter::defaults(obs_properties_t* props, obs_data_t* data) { bool is_int = false; size_t limit = 0; switch (_param->get_type()) { case gs::effect_parameter::type::Integer: is_int = true; limit = 1; break; case gs::effect_parameter::type::Integer2: is_int = true; limit = 2; break; case gs::effect_parameter::type::Integer3: is_int = true; limit = 3; break; case gs::effect_parameter::type::Integer4: is_int = true; limit = 4; break; case gs::effect_parameter::type::Float: is_int = false; limit = 1; break; case gs::effect_parameter::type::Float2: is_int = false; limit = 2; break; case gs::effect_parameter::type::Float3: is_int = false; limit = 3; break; case gs::effect_parameter::type::Float4: is_int = false; limit = 4; break; } for (size_t idx = 0; idx < limit; idx++) { if (is_int) { obs_data_set_default_int(data, _cache.name[idx].c_str(), _value.i[idx]); obs_data_set_int(data, _cache.name[idx].c_str(), _value.i[idx]); } else { obs_data_set_default_double(data, _cache.name[idx].c_str(), _value.f[idx]); obs_data_set_double(data, _cache.name[idx].c_str(), _value.f[idx]); } } } void gfx::effect_source::value_parameter::properties(obs_properties_t* props) { auto grp = props; if (!util::are_property_groups_broken()) { grp = obs_properties_create(); auto p = obs_properties_add_group(props, _name.c_str(), _visible_name.c_str(), OBS_GROUP_NORMAL, grp); obs_property_set_long_description(p, _description.c_str()); obs_property_set_visible(p, _visible); } bool is_int = false; size_t limit = 0; switch (_param->get_type()) { case gs::effect_parameter::type::Integer: is_int = true; limit = 1; break; case gs::effect_parameter::type::Integer2: is_int = true; limit = 2; break; case gs::effect_parameter::type::Integer3: is_int = true; limit = 3; break; case gs::effect_parameter::type::Integer4: is_int = true; limit = 4; break; case gs::effect_parameter::type::Float: is_int = false; limit = 1; break; case gs::effect_parameter::type::Float2: is_int = false; limit = 2; break; case gs::effect_parameter::type::Float3: is_int = false; limit = 3; break; case gs::effect_parameter::type::Float4: is_int = false; limit = 4; break; } for (size_t idx = 0; idx < limit; idx++) { if (is_int) { if (_mode == value_mode::INPUT) { auto p = obs_properties_add_int(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), _minimum.i[idx], _maximum.i[idx], _step.i[idx]); obs_property_set_visible(p, _visible); } else { auto p = obs_properties_add_int_slider(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), _minimum.i[idx], _maximum.i[idx], _step.i[idx]); obs_property_set_visible(p, _visible); } } else { if (_mode == value_mode::INPUT) { auto p = obs_properties_add_float(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), _minimum.f[idx], _maximum.f[idx], _step.f[idx]); obs_property_set_visible(p, _visible); } else { auto p = obs_properties_add_float_slider(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), _minimum.f[idx], _maximum.f[idx], _step.f[idx]); obs_property_set_visible(p, _visible); } } } } void gfx::effect_source::value_parameter::remove_properties(obs_properties_t* props) { bool is_int = false; size_t limit = 0; switch (_param->get_type()) { case gs::effect_parameter::type::Integer: is_int = true; limit = 1; break; case gs::effect_parameter::type::Integer2: is_int = true; limit = 2; break; case gs::effect_parameter::type::Integer3: is_int = true; limit = 3; break; case gs::effect_parameter::type::Integer4: is_int = true; limit = 4; break; case gs::effect_parameter::type::Float: is_int = false; limit = 1; break; case gs::effect_parameter::type::Float2: is_int = false; limit = 2; break; case gs::effect_parameter::type::Float3: is_int = false; limit = 3; break; case gs::effect_parameter::type::Float4: is_int = false; limit = 4; break; } for (size_t idx = 0; idx < limit; idx++) { obs_properties_remove_by_name(props, _cache.name[idx].c_str()); } } void gfx::effect_source::value_parameter::update(obs_data_t* data) { bool is_int = false; size_t limit = 0; switch (_param->get_type()) { case gs::effect_parameter::type::Integer: is_int = true; limit = 1; break; case gs::effect_parameter::type::Integer2: is_int = true; limit = 2; break; case gs::effect_parameter::type::Integer3: is_int = true; limit = 3; break; case gs::effect_parameter::type::Integer4: is_int = true; limit = 4; break; case gs::effect_parameter::type::Float: is_int = false; limit = 1; break; case gs::effect_parameter::type::Float2: is_int = false; limit = 2; break; case gs::effect_parameter::type::Float3: is_int = false; limit = 3; break; case gs::effect_parameter::type::Float4: is_int = false; limit = 4; break; } for (size_t idx = 0; idx < limit; idx++) { if (is_int) { _value.i[idx] = obs_data_get_int(data, _cache.name[idx].c_str()); } else { _value.f[idx] = static_cast(obs_data_get_double(data, _cache.name[idx].c_str())); } } } void gfx::effect_source::value_parameter::tick(float_t time) {} void gfx::effect_source::value_parameter::prepare() {} void gfx::effect_source::value_parameter::assign() { switch (_param->get_type()) { case gs::effect_parameter::type::Integer: _param->set_int(_value.i[0]); break; case gs::effect_parameter::type::Integer2: _param->set_int2(_value.i[0], _value.i[1]); break; case gs::effect_parameter::type::Integer3: _param->set_int3(_value.i[0], _value.i[1], _value.i[2]); break; case gs::effect_parameter::type::Integer4: _param->set_int4(_value.i[0], _value.i[1], _value.i[2], _value.i[3]); break; case gs::effect_parameter::type::Float: _param->set_float(_value.f[0]); break; case gs::effect_parameter::type::Float2: _param->set_float2(_value.f[0], _value.f[1]); break; case gs::effect_parameter::type::Float3: _param->set_float3(_value.f[0], _value.f[1], _value.f[2]); break; case gs::effect_parameter::type::Float4: _param->set_float4(_value.f[0], _value.f[1], _value.f[2], _value.f[3]); break; } } gfx::effect_source::matrix_parameter::matrix_parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : parameter(parent, effect, param) { std::shared_ptr min = param->get_annotation("minimum"); std::shared_ptr max = param->get_annotation("maximum"); std::shared_ptr stp = param->get_annotation("step"); param->get_default_matrix(_value); if (min) min->get_default_matrix(_minimum); else _minimum = matrix4{vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}}; if (max) max->get_default_matrix(_maximum); else _maximum = matrix4{vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}, vec4{-167772.16f, -167772.16f, -167772.16f, -167772.16f}}; if (stp) stp->get_default_matrix(_step); else _step = matrix4{vec4{0.01f, 0.01f, 0.01f, 0.01f}, vec4{0.01f, 0.01f, 0.01f, 0.01f}, vec4{0.01f, 0.01f, 0.01f, 0.01f}, vec4{0.01f, 0.01f, 0.01f, 0.01f}}; std::shared_ptr mode = param->get_annotation("mode"); if (mode && (mode->get_type() == gs::effect_parameter::type::String)) { std::string mode_str = mode->get_default_string(); if (strcmpi(mode_str.c_str(), "slider") == 0) { _mode = value_mode::SLIDER; } else { _mode = value_mode::INPUT; } } for (size_t x = 0; x < 4; x++) { for (size_t y = 0; y < 4; y++) { std::stringstream name_sstr; std::stringstream ui_sstr; name_sstr << _name << '[' << x << ']' << '[' << y << ']'; ui_sstr << _visible_name << '[' << x << ']' << '[' << y << ']'; _cache.name[x * 4 + y] = name_sstr.str(); _cache.visible_name[x * 4 + y] = ui_sstr.str(); } } } void gfx::effect_source::matrix_parameter::defaults(obs_properties_t* props, obs_data_t* data) { for (size_t x = 0; x < 4; x++) { vec4& v_ref = _value.x; if (x == 0) { vec4& v_ref = _value.x; } else if (x == 1) { vec4& v_ref = _value.y; } else if (x == 2) { vec4& v_ref = _value.z; } else { vec4& v_ref = _value.t; } for (size_t y = 0; y < 4; y++) { size_t idx = x * 4 + y; obs_data_set_double(data, _cache.name[idx].c_str(), v_ref.ptr[y]); obs_data_set_default_double(data, _cache.name[idx].c_str(), v_ref.ptr[y]); } } } void gfx::effect_source::matrix_parameter::properties(obs_properties_t* props) { auto grp = props; if (!util::are_property_groups_broken()) { grp = obs_properties_create(); auto p = obs_properties_add_group(props, _name.c_str(), _visible_name.c_str(), OBS_GROUP_NORMAL, grp); obs_property_set_long_description(p, _description.c_str()); obs_property_set_visible(p, _visible); } for (size_t x = 0; x < 4; x++) { vec4& min_ref = _minimum.x; vec4& max_ref = _maximum.x; vec4& stp_ref = _step.x; if (x == 0) { min_ref = _minimum.x; max_ref = _maximum.x; stp_ref = _step.x; } else if (x == 1) { min_ref = _minimum.y; max_ref = _maximum.y; stp_ref = _step.y; } else if (x == 2) { min_ref = _minimum.z; max_ref = _maximum.z; stp_ref = _step.z; } else { min_ref = _minimum.t; max_ref = _maximum.t; stp_ref = _step.t; } for (size_t y = 0; y < 4; y++) { size_t idx = x * 4 + y; if (_mode == value_mode::INPUT) { auto p = obs_properties_add_float(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), min_ref.ptr[y], max_ref.ptr[y], stp_ref.ptr[y]); obs_property_set_visible(p, _visible); } else { auto p = obs_properties_add_float_slider(grp, _cache.name[idx].c_str(), _cache.visible_name[idx].c_str(), min_ref.ptr[y], max_ref.ptr[y], stp_ref.ptr[y]); obs_property_set_visible(p, _visible); } } } } void gfx::effect_source::matrix_parameter::remove_properties(obs_properties_t* props) { for (size_t x = 0; x < 4; x++) { for (size_t y = 0; y < 4; y++) { size_t idx = x * 4 + y; obs_properties_remove_by_name(props, _cache.name[idx].c_str()); } } } void gfx::effect_source::matrix_parameter::update(obs_data_t* data) { for (size_t x = 0; x < 4; x++) { vec4& v_ref = _value.x; if (x == 0) { vec4& v_ref = _value.x; } else if (x == 1) { vec4& v_ref = _value.y; } else if (x == 2) { vec4& v_ref = _value.z; } else { vec4& v_ref = _value.t; } for (size_t y = 0; y < 4; y++) { size_t idx = x * 4 + y; v_ref.ptr[y] = obs_data_get_double(data, _cache.name[idx].c_str()); } } } void gfx::effect_source::matrix_parameter::tick(float_t time) {} void gfx::effect_source::matrix_parameter::prepare() {} void gfx::effect_source::matrix_parameter::assign() { _param->set_matrix(_value); } gfx::effect_source::string_parameter::string_parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : parameter(parent, effect, param) { param->get_default_string(_value); } void gfx::effect_source::string_parameter::defaults(obs_properties_t* props, obs_data_t* data) {} void gfx::effect_source::string_parameter::properties(obs_properties_t* props) {} void gfx::effect_source::string_parameter::remove_properties(obs_properties_t* props) {} void gfx::effect_source::string_parameter::update(obs_data_t* data) {} void gfx::effect_source::string_parameter::tick(float_t time) {} void gfx::effect_source::string_parameter::prepare() {} void gfx::effect_source::string_parameter::assign() {} void gfx::effect_source::texture_parameter::load_texture(std::string file) { _file_name = file; struct stat st; if (os_stat(_file_name.c_str(), &st) == -1) { _last_size = 0; _last_modify_time = 0; _last_create_time = 0; throw std::system_error(std::error_code(ENOENT, std::system_category()), file.c_str()); } else { _last_size = st.st_size; _last_modify_time = st.st_mtime; _last_create_time = st.st_ctime; } _file = std::make_shared(_file_name); } gfx::effect_source::texture_parameter::texture_parameter(std::shared_ptr parent, std::shared_ptr effect, std::shared_ptr param) : parameter(parent, effect, param) { _cache.name[0] = _name + ".Type"; _cache.visible_name[0] = _visible_name + " " + D_TRANSLATE(ST_TEXTURE_TYPE); _cache.name[1] = _name + ".File"; _cache.visible_name[1] = _visible_name; _cache.name[2] = _name + ".Source"; _cache.visible_name[2] = _visible_name; } bool gfx::effect_source::texture_parameter::modified2(obs_properties_t* props, obs_property_t* property, obs_data_t* settings) { _mode = static_cast(obs_data_get_int(settings, _cache.name[0].c_str())); if (_mode == texture_mode::FILE) { obs_property_set_visible(obs_properties_get(props, _cache.name[1].c_str()), true); obs_property_set_visible(obs_properties_get(props, _cache.name[2].c_str()), false); } else if (_mode == texture_mode::SOURCE) { obs_property_set_visible(obs_properties_get(props, _cache.name[1].c_str()), false); obs_property_set_visible(obs_properties_get(props, _cache.name[2].c_str()), true); } return true; } void gfx::effect_source::texture_parameter::defaults(obs_properties_t* props, obs_data_t* data) { obs_data_set_int(data, _cache.name[0].c_str(), static_cast(_mode)); obs_data_set_string(data, _cache.name[1].c_str(), _file_name.c_str()); obs_data_set_string(data, _cache.name[2].c_str(), _source_name.c_str()); } void gfx::effect_source::texture_parameter::properties(obs_properties_t* props) { auto grp = props; if (!util::are_property_groups_broken()) { grp = obs_properties_create(); auto p = obs_properties_add_group(props, _name.c_str(), _visible_name.c_str(), OBS_GROUP_NORMAL, grp); obs_property_set_long_description(p, _description.c_str()); obs_property_set_visible(p, _visible); } { auto p = obs_properties_add_list(grp, _cache.name[0].c_str(), _cache.visible_name[0].c_str(), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_modified_callback2( p, [](void* priv, obs_properties_t* props, obs_property_t* property, obs_data_t* settings) { return reinterpret_cast(priv)->modified2(props, property, settings); }, this); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_TEXTURE_TYPE))); obs_property_list_add_int(p, D_TRANSLATE(ST_TEXTURE_FILE), static_cast(texture_mode::FILE)); obs_property_list_add_int(p, D_TRANSLATE(ST_TEXTURE_SOURCE), static_cast(texture_mode::SOURCE)); } { auto p = obs_properties_add_path(grp, _cache.name[1].c_str(), _cache.visible_name[1].c_str(), OBS_PATH_FILE, "Textures (" S_FILEFILTERS_TEXTURE ")", nullptr); obs_property_set_long_description(p, _description.c_str()); } { auto p = obs_properties_add_list(grp, _cache.name[2].c_str(), _cache.visible_name[2].c_str(), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs::source_tracker::get()->enumerate( [&p](std::string name, obs_source_t*) { obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str()); return false; }, obs::source_tracker::filter_video_sources); obs::source_tracker::get()->enumerate( [&p](std::string name, obs_source_t*) { obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str()); return false; }, obs::source_tracker::filter_scenes); obs_property_set_long_description(p, _description.c_str()); } } void gfx::effect_source::texture_parameter::remove_properties(obs_properties_t* props) {} void gfx::effect_source::texture_parameter::update(obs_data_t* data) { try { if (_mode == texture_mode::FILE) { load_texture(obs_data_get_string(data, _cache.name[1].c_str())); } else if (_mode == texture_mode::SOURCE) { _source_name = obs_data_get_string(data, _cache.name[2].c_str()); if (!_source || (obs_source_get_name(_source->get()) != _source_name)) { _source = std::make_shared(_source_name); if (!_parent.expired()) _source_renderer = std::make_shared(_source, _parent.lock()->get_self()); } } } catch (std::exception& ex) { P_LOG_ERROR("Update failed, error: %s", ex.what()); } } void gfx::effect_source::texture_parameter::tick(float_t time) { _last_check += time; if (_last_check >= 0.5f) { _last_check -= 0.5f; bool changed = false; struct stat st; if (os_stat(_file_name.c_str(), &st) != -1) { changed = (_last_size != st.st_size) || (_last_modify_time != st.st_mtime) || (_last_create_time != st.st_ctime); } if (changed) { try { load_texture(_file_name); } catch (std::exception& ex) { P_LOG_ERROR("Loading texture \"%s\" failed, error: %s", _file_name.c_str(), ex.what()); } } } } void gfx::effect_source::texture_parameter::prepare() { if (_mode == texture_mode::SOURCE) { if (_source_renderer) { _source_tex = _source_renderer->render(_source->width(), _source->height()); } } } void gfx::effect_source::texture_parameter::assign() { if (_mode == texture_mode::FILE) { if (_file) _param->set_texture(_file); } else { if (_source_tex) _param->set_texture(_source_tex); } } bool gfx::effect_source::effect_source::modified2(obs_properties_t* props, obs_property_t* property, obs_data_t* settings) { // Broken, gets stuck locking gs::context. /* auto gctx = gs::context(); for (auto& kv : _params) { if (kv.second) kv.second->remove_properties(props); } try { const char* str = obs_data_get_string(settings, ST_FILE); load_file(str ? str : ""); } catch (std::exception& ex) { P_LOG_ERROR(" Failed to load effect \"%s\" due to error: %s", _file.c_str(), ex.what()); } for (auto& kv : _params) { if (kv.second) kv.second->properties(props); }*/ for (auto& kv : _params) { if (kv.second) kv.second->defaults(props, settings); } return true; } void gfx::effect_source::effect_source::load_file(std::string file) { auto gctx = gs::context(); _params.clear(); _effect.reset(); _file = file; _time = 0; _time_active = 0; struct stat st; if (os_stat(_file.c_str(), &st) == -1) { _last_size = 0; _last_modify_time = 0; _last_create_time = 0; throw std::system_error(std::error_code(ENOENT, std::system_category()), file.c_str()); } else { _last_size = st.st_size; _last_modify_time = st.st_mtime; _last_create_time = st.st_ctime; } _effect = gs::effect::create(file); auto prms = _effect->get_parameters(); for (auto prm : prms) { param_ident_t identity; identity.first = prm->get_type(); identity.second = prm->get_name(); bool skip = false; for (auto v : static_parameters) { if (prm->get_name() == v) { skip = true; break; } } if (_cb_valid) skip = skip || !_cb_valid(prm); if (skip) continue; _params.emplace(identity, parameter::create(this->shared_from_this(), _effect, prm)); } } gfx::effect_source::effect_source::effect_source(obs_source_t* self) : _self(self), _last_check(0), _last_size(0), _last_modify_time(0), _last_create_time(0), _time(0), _time_active(0), _time_since_last_tick(0) { auto gctx = gs::context(); _tri = std::make_shared(3ul, uint8_t(1)); { auto& vtx = _tri->at(0); vec3_set(vtx.position, 0, 0, 0); vec4_set(vtx.uv[0], 0, 0, 0, 0); } { auto& vtx = _tri->at(1); vec3_set(vtx.position, 2, 0, 0); vec4_set(vtx.uv[0], 2, 0, 0, 0); } { auto& vtx = _tri->at(2); vec3_set(vtx.position, 0, 2, 0); vec4_set(vtx.uv[0], 0, 2, 0, 0); } } gfx::effect_source::effect_source::~effect_source() {} void gfx::effect_source::effect_source::properties(obs_properties_t* props) { auto p = obs_properties_add_path(props, ST_FILE, D_TRANSLATE(ST_FILE), OBS_PATH_FILE, "Effects (*.effect);;*.*", nullptr); obs_property_set_modified_callback2( p, [](void* priv, obs_properties_t* props, obs_property_t* property, obs_data_t* settings) { return reinterpret_cast(priv)->modified2(props, property, settings); }, this); obs_properties_add_text(props, ST_TECHNIQUE, D_TRANSLATE(ST_TECHNIQUE), OBS_TEXT_DEFAULT); for (auto& kv : _params) { if (kv.second) kv.second->properties(props); } } void gfx::effect_source::effect_source::update(obs_data_t* data) { const char* file = obs_data_get_string(data, ST_FILE); if (file != _file) { try { load_file(file); } catch (std::exception& ex) { P_LOG_ERROR(" Failed to load effect \"%s\" due to error: %s", _file.c_str(), ex.what()); } } const char* str = obs_data_get_string(data, ST_TECHNIQUE); _tech = str ? str : "Draw"; for (auto& kv : _params) { if (kv.second) kv.second->update(data); } } bool gfx::effect_source::effect_source::tick(float_t time) { _last_check += time; if (_last_check >= 0.5f) { _last_check -= 0.5f; bool changed = false; struct stat st; if (os_stat(_file.c_str(), &st) != -1) { changed = (_last_size != st.st_size) || (_last_modify_time != st.st_mtime) || (_last_create_time != st.st_ctime); } if (changed) { try { load_file(_file); } catch (std::exception& ex) { P_LOG_ERROR("Loading shader \"%s\" failed, error: %s", _file.c_str(), ex.what()); } return true; } } for (auto& kv : _params) { if (kv.second) kv.second->tick(time); } _time += time; _time_since_last_tick = time; return false; } void gfx::effect_source::effect_source::render() { if (!_effect) return; for (auto& kv : _params) { if (kv.second) kv.second->prepare(); } for (auto& kv : _params) { if (kv.second) kv.second->assign(); } // Apply "special" parameters. _time_active += _time_since_last_tick; { auto p_time = _effect->get_parameter("Time"); if (p_time && (p_time->get_type() == gs::effect_parameter::type::Float4)) { p_time->set_float4(_time, _time_active, _time_since_last_tick, _random_dist(_random_generator)); } auto p_random = _effect->get_parameter("Random"); if (p_random && (p_random->get_type() == gs::effect_parameter::type::Matrix)) { matrix4 m; vec4_set(&m.x, _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator)); vec4_set(&m.y, _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator)); vec4_set(&m.z, _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator)); vec4_set(&m.t, _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator), _random_dist(_random_generator)); p_random->set_matrix(m); } } if (_cb_override) { _cb_override(_effect); } gs_blend_state_push(); gs_matrix_push(); gs_reset_blend_state(); gs_enable_blending(false); gs_enable_color(true, true, true, true); gs_enable_depth_test(false); gs_enable_stencil_test(false); gs_enable_stencil_write(false); gs_ortho(0, 1, 0, 1, -1., 1.); while (gs_effect_loop(_effect->get_object(), _tech.c_str())) { gs_load_vertexbuffer(_tri->update()); gs_load_indexbuffer(nullptr); gs_draw(gs_draw_mode::GS_TRIS, 0, _tri->size()); } gs_matrix_pop(); gs_blend_state_pop(); } obs_source_t* gfx::effect_source::effect_source::get_self() { return _self; } void gfx::effect_source::effect_source::set_valid_property_cb(valid_property_cb_t cb) { _cb_valid = cb; } void gfx::effect_source::effect_source::set_override_cb(param_override_cb_t cb) { _cb_override = cb; }