diff --git a/source/gfx-effect-source.cpp b/source/gfx-effect-source.cpp index 9afb1450..1fb99012 100644 --- a/source/gfx-effect-source.cpp +++ b/source/gfx-effect-source.cpp @@ -20,7 +20,7 @@ #include #include -bool gfx::effect_source::property_type_modified(void* priv, obs_properties_t* props, obs_property_t* prop, obs_data_t* sett) { +bool gfx::effect_source::property_type_modified(void*, obs_properties_t* props, obs_property_t*, obs_data_t* sett) { switch ((InputTypes)obs_data_get_int(sett, D_TYPE)) { default: case InputTypes::Text: @@ -35,22 +35,43 @@ bool gfx::effect_source::property_type_modified(void* priv, obs_properties_t* pr return true; } -bool gfx::effect_source::property_input_modified(void* priv, obs_properties_t* props, obs_property_t* prop, obs_data_t* sett) { - return true; +bool gfx::effect_source::property_input_modified(void* obj, obs_properties_t*, obs_property_t*, obs_data_t* sett) { + const char* text = nullptr; + const char* file = nullptr; + + switch ((InputTypes)obs_data_get_int(sett, D_TYPE)) { + default: + case InputTypes::Text: + text = obs_data_get_string(sett, D_INPUT_TEXT); + break; + case InputTypes::File: + file = obs_data_get_string(sett, D_INPUT_FILE); + break; + } + + return reinterpret_cast(obj)->test_for_updates(text, file); +} + +void gfx::effect_source::video_tick_impl(float time) { + // Shader Timers + time_existing += time; + time_active += time; + + // File Timer + shader.file_info.time_updated -= time; +} + +void gfx::effect_source::video_render_impl(gs_effect_t* parent_effect) { + } gfx::effect_source::effect_source(obs_data_t* data, obs_source_t* owner) { - obs_source_addref(owner); m_source = owner; time_existing = 0; time_active = 0; - - update(data); } -gfx::effect_source::~effect_source() { - obs_source_release(m_source); -} +gfx::effect_source::~effect_source() {} void gfx::effect_source::get_properties(obs_properties_t* properties) { obs_property_t* p = nullptr; @@ -66,7 +87,7 @@ void gfx::effect_source::get_properties(obs_properties_t* properties) { obs_property_set_modified_callback2(p, property_input_modified, this); { - char* tmp_path = obs_module_file("shaders/"); + char* tmp_path = obs_module_file(default_shader_path.c_str()); p = obs_properties_add_path(properties, D_INPUT_FILE, P_TRANSLATE(T_INPUT_FILE), OBS_PATH_FILE, "Any (*.effect *.shader *.hlsl);;Effect (*.effect);;Shader (*.shader);;DirectX (*.hlsl)", tmp_path); obs_property_set_long_description(p, P_TRANSLATE(P_DESC(T_INPUT_FILE))); @@ -75,6 +96,29 @@ void gfx::effect_source::get_properties(obs_properties_t* properties) { } // ToDo: Place updated properties here or somewhere else? + for (auto prm : parameters) { + if (prm.first.second == gs::effect_parameter::type::Boolean) { + obs_properties_add_bool(properties, prm.second->ui.names[0], prm.second->ui.descs[0]); + } else if (prm.first.second >= gs::effect_parameter::type::Integer && prm.first.second <= gs::effect_parameter::type::Integer4) { + size_t cnt = (size_t)prm.first.second - (size_t)gs::effect_parameter::type::Integer; + + for (size_t idx = 0; idx <= cnt; idx++) { + obs_properties_add_int(properties, prm.second->ui.names[idx], prm.second->ui.descs[idx], INT_MIN, INT_MAX, 1); + } + } else if (prm.first.second >= gs::effect_parameter::type::Float && prm.first.second <= gs::effect_parameter::type::Float4) { + size_t cnt = (size_t)prm.first.second - (size_t)gs::effect_parameter::type::Float; + + for (size_t idx = 0; idx <= cnt; idx++) { + obs_properties_add_float(properties, prm.second->ui.names[idx], prm.second->ui.descs[idx], FLT_MIN, FLT_MAX, 1); + } + } + } +} + +void gfx::effect_source::get_defaults(obs_data_t* data) { + obs_data_set_default_int(data, D_TYPE, (long long)InputTypes::Text); + obs_data_set_default_string(data, D_INPUT_TEXT, ""); + obs_data_set_default_string(data, D_INPUT_FILE, ""); } void gfx::effect_source::update(obs_data_t* data) { @@ -90,57 +134,59 @@ void gfx::effect_source::update(obs_data_t* data) { test_for_updates(nullptr, path); } + update_parameters(data); + obs_data_release(data); } bool gfx::effect_source::test_for_updates(const char* text, const char* path) { bool is_shader_different = false; if (text != nullptr) { - if (text != effect.text) { - effect.text = text; + if (text != shader.text) { + shader.text = text; is_shader_different = true; } if (is_shader_different) { - effect.effect = std::make_unique(effect.text, "Text"); + shader.effect = std::make_unique(shader.text, "Text"); } } else if (path != nullptr) { - if (path != this->effect.path) { - this->effect.path = path; - this->effect.file_info.time_updated = 0; - this->effect.file_info.time_create = 0; - this->effect.file_info.time_modified = 0; - this->effect.file_info.file_size = 0; + if (path != this->shader.path) { + this->shader.path = path; + this->shader.file_info.time_updated = 0; + this->shader.file_info.time_create = 0; + this->shader.file_info.time_modified = 0; + this->shader.file_info.file_size = 0; is_shader_different = true; } // If the update timer is 0 or less, grab new file information. - if (effect.file_info.time_updated <= 0) { + if (shader.file_info.time_updated <= 0) { struct stat stats; - if (os_stat(effect.path.c_str(), &stats) == 0) { - effect.file_info.modified = (effect.file_info.time_create != stats.st_ctime) - | (effect.file_info.time_modified != stats.st_mtime) - | (effect.file_info.file_size != stats.st_size); + if (os_stat(shader.path.c_str(), &stats) == 0) { + shader.file_info.modified = (shader.file_info.time_create != stats.st_ctime) + | (shader.file_info.time_modified != stats.st_mtime) + | (shader.file_info.file_size != stats.st_size); // Mark shader as different if the file was changed. is_shader_different = is_shader_different - | effect.file_info.modified; + | shader.file_info.modified; // Update own information - effect.file_info.time_create = stats.st_ctime; - effect.file_info.time_modified = stats.st_mtime; - effect.file_info.file_size = stats.st_size; + shader.file_info.time_create = stats.st_ctime; + shader.file_info.time_modified = stats.st_mtime; + shader.file_info.file_size = stats.st_size; } // Increment timer so that the next check is a reasonable timespan away. - effect.file_info.time_updated += 0.1f; + shader.file_info.time_updated += 0.1f; } - if (is_shader_different || effect.file_info.modified) { + if (is_shader_different || shader.file_info.modified) { // gs_effect_create_from_file caches results, which is bad for us. std::vector content; - std::ifstream fs(effect.path.c_str(), std::ios::binary); + std::ifstream fs(shader.path.c_str(), std::ios::binary); if (fs.good()) { size_t beg = fs.tellg(); @@ -152,21 +198,185 @@ bool gfx::effect_source::test_for_updates(const char* text, const char* path) { fs.close(); content[sz] = '\0'; - effect.effect = std::make_unique(std::string(content.data()), effect.path); + shader.effect = std::make_unique(std::string(content.data()), shader.path); } } } // If the shader is different, rebuild the parameter list. if (is_shader_different) { + std::map> new_params; + // ToDo: Figure out if a recycling approach would work. + // Might improve stability in low memory situations. + auto effect_param_list = shader.effect->get_parameters(); + for (auto effect_param : effect_param_list) { + paramident_t ident; + ident.first = effect_param.get_name(); + ident.second = effect_param.get_type(); + + if (is_special_parameter(ident.first, ident.second)) + continue; + + auto entry = parameters.find(ident); + if (entry != parameters.end()) { + entry->second->param = std::make_shared(effect_param); + new_params.insert_or_assign(ident, entry->second); + parameters.erase(entry); + } else { + std::shared_ptr param; + + if (ident.second == gs::effect_parameter::type::Boolean) { + std::shared_ptr nparam = std::make_shared(); + + std::string ui_name, ui_desc; + ui_name = ident.first; + ui_desc = ident.first; + + nparam->ui.buffer.resize(ui_name.size() + 1 + ui_desc.size() + 1); + memset(nparam->ui.buffer.data(), 0, nparam->ui.buffer.size()); + memcpy(nparam->ui.buffer.data(), ui_name.c_str(), ui_name.size()); + memcpy(nparam->ui.buffer.data() + ui_name.size() + 1, ui_desc.c_str(), ui_desc.size()); + + nparam->ui.names.resize(1); + nparam->ui.names[0] = nparam->ui.buffer.data(); + + nparam->ui.descs.resize(1); + nparam->ui.descs[0] = nparam->ui.buffer.data() + ui_name.size() + 1; + + param = std::dynamic_pointer_cast(nparam); + } else if (ident.second >= gs::effect_parameter::type::Integer && ident.second <= gs::effect_parameter::type::Integer4) { + std::shared_ptr nparam = std::make_shared(); + + size_t cnt = (size_t)ident.second - (size_t)gs::effect_parameter::type::Integer; + + std::string ui_name[4], ui_desc[4]; + size_t bufsize = 0; + if (cnt > 0) { + for (size_t idx = 0; idx <= cnt; idx++) { + ui_name[idx] = ident.first + (char)(48 + idx); + ui_desc[idx] = ident.first + "[" + (char)(48 + idx) + "]"; + + bufsize += ui_name[idx].size() + 1; + bufsize += ui_desc[idx].size() + 1; + } + } else { + ui_name[0] = ident.first; + ui_desc[0] = ident.first; + bufsize += ui_name[0].size() + 1; + bufsize += ui_desc[0].size() + 1; + } + + nparam->ui.names.resize(cnt + 1); + nparam->ui.descs.resize(cnt + 1); + + nparam->ui.buffer.resize(bufsize); + memset(nparam->ui.buffer.data(), 0, bufsize); + size_t off = 0; + for (size_t idx = 0; idx <= cnt; idx++) { + memcpy(nparam->ui.buffer.data() + off, ui_name[idx].c_str(), ui_name[idx].size()); + nparam->ui.names[idx] = nparam->ui.buffer.data() + off; + off += ui_name[idx].size() + 1; + + memcpy(nparam->ui.buffer.data() + off, ui_desc[idx].c_str(), ui_desc[idx].size()); + nparam->ui.descs[idx] = nparam->ui.buffer.data() + off; + off += ui_desc[idx].size() + 1; + } + + param = std::dynamic_pointer_cast(nparam); + } else if (ident.second >= gs::effect_parameter::type::Float && ident.second <= gs::effect_parameter::type::Float4) { + std::shared_ptr nparam = std::make_shared(); + + size_t cnt = (size_t)ident.second - (size_t)gs::effect_parameter::type::Float; + + std::string ui_name[4], ui_desc[4]; + size_t bufsize = 0; + if (cnt > 0) { + for (size_t idx = 0; idx <= cnt; idx++) { + ui_name[idx] = ident.first + (char)(48 + idx); + ui_desc[idx] = ident.first + "[" + (char)(48 + idx) + "]"; + + bufsize += ui_name[idx].size() + 1; + bufsize += ui_desc[idx].size() + 1; + } + } else { + ui_name[0] = ident.first; + ui_desc[0] = ident.first; + bufsize += ui_name[0].size() + 1; + bufsize += ui_desc[0].size() + 1; + } + + nparam->ui.names.resize(cnt + 1); + nparam->ui.descs.resize(cnt + 1); + + nparam->ui.buffer.resize(bufsize); + memset(nparam->ui.buffer.data(), 0, bufsize); + size_t off = 0; + for (size_t idx = 0; idx <= cnt; idx++) { + memcpy(nparam->ui.buffer.data() + off, ui_name[idx].c_str(), ui_name[idx].size()); + nparam->ui.names[idx] = nparam->ui.buffer.data() + off; + off += ui_name[idx].size() + 1; + + memcpy(nparam->ui.buffer.data() + off, ui_desc[idx].c_str(), ui_desc[idx].size()); + nparam->ui.descs[idx] = nparam->ui.buffer.data() + off; + off += ui_desc[idx].size() + 1; + } + + param = std::dynamic_pointer_cast(nparam); + } else { + + } + + if (param) { + param->name = ident.first; + param->param = std::make_shared(effect_param); + new_params.insert_or_assign(ident, param); + } + } + } + + parameters = std::move(new_params); } return is_shader_different; } -void gfx::effect_source::active() { +void gfx::effect_source::update_parameters(obs_data_t* data) { + for (auto prm : parameters) { + if (prm.first.second == gs::effect_parameter::type::Boolean) { + auto param = std::static_pointer_cast(prm.second); + param->value = obs_data_get_bool(data, prm.second->ui.names[0]); + } else if (prm.first.second >= gs::effect_parameter::type::Integer && prm.first.second <= gs::effect_parameter::type::Integer4) { + auto param = std::static_pointer_cast(prm.second); + for (size_t idx = 0; idx < prm.second->ui.names.size(); idx++) { + param->value[idx] = obs_data_get_int(data, prm.second->ui.names[idx]); + } + } else if (prm.first.second >= gs::effect_parameter::type::Float && prm.first.second <= gs::effect_parameter::type::Float4) { + auto param = std::static_pointer_cast(prm.second); + for (size_t idx = 0; idx < prm.second->ui.names.size(); idx++) { + param->value[idx] = obs_data_get_double(data, prm.second->ui.names[idx]); + } + } + } +} + +void gfx::effect_source::apply_parameters() { + for (auto prm : parameters) { + if (prm.first.second == gs::effect_parameter::type::Boolean) { + auto param = std::static_pointer_cast(prm.second); + + } else if (prm.first.second >= gs::effect_parameter::type::Integer && prm.first.second <= gs::effect_parameter::type::Integer4) { + auto param = std::static_pointer_cast(prm.second); + + } else if (prm.first.second >= gs::effect_parameter::type::Float && prm.first.second <= gs::effect_parameter::type::Float4) { + auto param = std::static_pointer_cast(prm.second); + + } + } +} + +void gfx::effect_source::activate() { time_active = 0; } @@ -174,6 +384,10 @@ void gfx::effect_source::deactivate() { time_active = 0; } +std::string gfx::effect_source::get_shader_file() { + return shader.path; +} + uint32_t gfx::effect_source::get_width() { return 0; } @@ -183,14 +397,9 @@ uint32_t gfx::effect_source::get_height() { } void gfx::effect_source::video_tick(float time) { - // Shader Timers - time_existing += time; - time_active += time; - - // File Timer - effect.file_info.time_updated -= time; + video_tick_impl(time); } void gfx::effect_source::video_render(gs_effect_t* parent_effect) { - + video_render_impl(parent_effect); } diff --git a/source/gfx-effect-source.h b/source/gfx-effect-source.h index c1f601d1..67af9a37 100644 --- a/source/gfx-effect-source.h +++ b/source/gfx-effect-source.h @@ -24,6 +24,8 @@ #include "gs-texture.h" #include "gfx-source-texture.h" #include +#include +#include // Data Defines #define D_TYPE "CustomShader.Type" @@ -39,12 +41,13 @@ namespace gfx { class effect_source { + protected: obs_source_t* m_source; std::unique_ptr m_renderTarget; // Effect Information struct { - std::unique_ptr effect; + std::shared_ptr effect; std::string text; std::string path; struct { @@ -54,28 +57,89 @@ namespace gfx { size_t file_size; bool modified; } file_info; - } effect; + } shader; + + struct parameter { + std::string name = ""; + std::shared_ptr param; + + struct { + std::vector buffer; + std::vector names; + std::vector descs; + } ui; + }; + struct bool_parameter : parameter { + bool value = false; + }; + struct int_parameter : parameter { + int32_t value[4] = { 0,0,0,0 }; + }; + struct float_parameter : parameter { + float_t value[4] = { 0,0,0,0 }; + }; + struct texture_parameter : parameter { + bool isSource = false; + + struct { + std::string path = ""; + std::shared_ptr tex; + struct { + float_t time_updated = 0; + time_t time_create = 0; + time_t time_modified = 0; + size_t file_size = 0; + bool modified = true; + } info; + } file; + struct { + std::string name = ""; + std::shared_ptr tex; + } source; + + struct { + bool doResample = false; + std::pair resolution = { 10, 10 }; + std::shared_ptr rt; + } resample; + }; + struct matrix_parameter : parameter { + + }; + + typedef std::pair paramident_t; + std::map> parameters; // Status float_t time_existing; float_t time_active; - protected: + std::string default_shader_path = "shaders/"; + static bool property_type_modified(void* priv, obs_properties_t* props, obs_property_t* prop, obs_data_t* sett); static bool property_input_modified(void* priv, obs_properties_t* props, obs_property_t* prop, obs_data_t* sett); + virtual bool is_special_parameter(std::string name, gs::effect_parameter::type type) = 0; + virtual bool apply_special_parameters() = 0; + + virtual void video_tick_impl(float time) = 0; + virtual void video_render_impl(gs_effect_t* parent_effect) = 0; + public: effect_source(obs_data_t* data, obs_source_t* owner); ~effect_source(); void get_properties(obs_properties_t* properties); - void get_defaults(obs_data_t* data); + static void get_defaults(obs_data_t* data); void update(obs_data_t* data); bool test_for_updates(const char* text, const char* path); void update_parameters(obs_data_t* data); + void apply_parameters(); - void active(); + void activate(); void deactivate(); + + std::string get_shader_file(); uint32_t get_width(); uint32_t get_height();