mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-11-27 22:03:01 +00:00
gfx-effect-source: Base class for Custom Shaders
gfx::effect_source is the base class for all Custom Shader Sources and Transitions, which reduces the overall workload to a single file, but unfortunately also reducing the effective customization per source a bit.
This commit is contained in:
parent
9ae8ecc3e1
commit
5e9f113553
2 changed files with 318 additions and 45 deletions
|
@ -20,7 +20,7 @@
|
|||
#include <libobs/util/platform.h>
|
||||
#include <fstream>
|
||||
|
||||
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<gfx::effect_source*>(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<gs::effect>(effect.text, "Text");
|
||||
shader.effect = std::make_unique<gs::effect>(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<char> 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<gs::effect>(std::string(content.data()), effect.path);
|
||||
shader.effect = std::make_unique<gs::effect>(std::string(content.data()), shader.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the shader is different, rebuild the parameter list.
|
||||
if (is_shader_different) {
|
||||
std::map<paramident_t, std::shared_ptr<parameter>> 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<gs::effect_parameter>(effect_param);
|
||||
new_params.insert_or_assign(ident, entry->second);
|
||||
parameters.erase(entry);
|
||||
} else {
|
||||
std::shared_ptr<parameter> param;
|
||||
|
||||
if (ident.second == gs::effect_parameter::type::Boolean) {
|
||||
std::shared_ptr<bool_parameter> nparam = std::make_shared<bool_parameter>();
|
||||
|
||||
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<parameter>(nparam);
|
||||
} else if (ident.second >= gs::effect_parameter::type::Integer && ident.second <= gs::effect_parameter::type::Integer4) {
|
||||
std::shared_ptr<int_parameter> nparam = std::make_shared<int_parameter>();
|
||||
|
||||
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<parameter>(nparam);
|
||||
} else if (ident.second >= gs::effect_parameter::type::Float && ident.second <= gs::effect_parameter::type::Float4) {
|
||||
std::shared_ptr<float_parameter> nparam = std::make_shared<float_parameter>();
|
||||
|
||||
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<parameter>(nparam);
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if (param) {
|
||||
param->name = ident.first;
|
||||
param->param = std::make_shared<gs::effect_parameter>(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<bool_parameter>(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<int_parameter>(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<float_parameter>(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<bool_parameter>(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<int_parameter>(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<float_parameter>(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);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include "gs-texture.h"
|
||||
#include "gfx-source-texture.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
// 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<gs::rendertarget> m_renderTarget;
|
||||
|
||||
// Effect Information
|
||||
struct {
|
||||
std::unique_ptr<gs::effect> effect;
|
||||
std::shared_ptr<gs::effect> effect;
|
||||
std::string text;
|
||||
std::string path;
|
||||
struct {
|
||||
|
@ -54,29 +57,90 @@ namespace gfx {
|
|||
size_t file_size;
|
||||
bool modified;
|
||||
} file_info;
|
||||
} effect;
|
||||
} shader;
|
||||
|
||||
struct parameter {
|
||||
std::string name = "";
|
||||
std::shared_ptr<gs::effect_parameter> param;
|
||||
|
||||
struct {
|
||||
std::vector<char> buffer;
|
||||
std::vector<const char*> names;
|
||||
std::vector<const char*> 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<gs::texture> 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<gfx::source_texture> tex;
|
||||
} source;
|
||||
|
||||
struct {
|
||||
bool doResample = false;
|
||||
std::pair<uint32_t, uint32_t> resolution = { 10, 10 };
|
||||
std::shared_ptr<gs::rendertarget> rt;
|
||||
} resample;
|
||||
};
|
||||
struct matrix_parameter : parameter {
|
||||
|
||||
};
|
||||
|
||||
typedef std::pair<std::string, gs::effect_parameter::type> paramident_t;
|
||||
std::map<paramident_t, std::shared_ptr<parameter>> 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();
|
||||
void video_tick(float time);
|
||||
|
|
Loading…
Reference in a new issue