2017-11-06 11:36:01 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2019-08-07 16:05:49 +00:00
|
|
|
#include "filter-shader.hpp"
|
2019-01-14 10:23:21 +00:00
|
|
|
#include "strings.hpp"
|
2019-08-07 10:43:23 +00:00
|
|
|
#include "utility.hpp"
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
#define ST "Filter.Shader"
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
P_INITIALIZER(FilterShaderInit)
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
initializer_functions.push_back([] { filter::shader::shader_factory::initialize(); });
|
|
|
|
finalizer_functions.push_back([] { filter::shader::shader_factory::finalize(); });
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
static std::shared_ptr<filter::shader::shader_factory> factory_instance = nullptr;
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_factory::initialize()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
factory_instance = std::make_shared<filter::shader::shader_factory>();
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_factory::finalize()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
factory_instance.reset();
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
std::shared_ptr<filter::shader::shader_factory> filter::shader::shader_factory::get()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
return factory_instance;
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 16:05:49 +00:00
|
|
|
static void get_defaults(obs_data_t* data)
|
|
|
|
{
|
|
|
|
obs_data_set_default_string(data, S_SHADER_FILE, obs_module_file("shaders/filter/example.effect"));
|
|
|
|
obs_data_set_default_string(data, S_SHADER_TECHNIQUE, "Draw");
|
|
|
|
}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
filter::shader::shader_factory::shader_factory()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
memset(&_source_info, 0, sizeof(obs_source_info));
|
|
|
|
_source_info.id = "obs-stream-effects-filter-shader";
|
|
|
|
_source_info.type = OBS_SOURCE_TYPE_FILTER;
|
|
|
|
_source_info.output_flags = OBS_SOURCE_VIDEO;
|
|
|
|
_source_info.get_name = [](void*) { return D_TRANSLATE(ST); };
|
|
|
|
_source_info.get_defaults = get_defaults;
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
_source_info.create = [](obs_data_t* data, obs_source_t* self) {
|
|
|
|
try {
|
|
|
|
return static_cast<void*>(new filter::shader::shader_instance(data, self));
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to create source, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to create source.");
|
|
|
|
}
|
|
|
|
return static_cast<void*>(nullptr);
|
|
|
|
};
|
|
|
|
_source_info.destroy = [](void* ptr) {
|
|
|
|
try {
|
|
|
|
delete reinterpret_cast<filter::shader::shader_instance*>(ptr);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to delete source, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to delete source.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
_source_info.get_properties = [](void* ptr) {
|
|
|
|
obs_properties_t* pr = obs_properties_create();
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->properties(pr);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve options, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve options.");
|
|
|
|
}
|
|
|
|
return pr;
|
|
|
|
};
|
2019-08-07 16:05:49 +00:00
|
|
|
_source_info.get_width = [](void* ptr) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
return reinterpret_cast<filter::shader::shader_instance*>(ptr)->width();
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve width, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve width.");
|
|
|
|
}
|
|
|
|
return uint32_t(0);
|
|
|
|
};
|
|
|
|
_source_info.get_height = [](void* ptr) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
return reinterpret_cast<filter::shader::shader_instance*>(ptr)->height();
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve height, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to retrieve height.");
|
|
|
|
}
|
|
|
|
return uint32_t(0);
|
|
|
|
};
|
2019-08-07 10:43:23 +00:00
|
|
|
_source_info.update = [](void* ptr, obs_data_t* data) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->update(data);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to update, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to update.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
_source_info.activate = [](void* ptr) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->activate();
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to activate, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to activate.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
_source_info.deactivate = [](void* ptr) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->deactivate();
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to deactivate, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to deactivate.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
_source_info.video_tick = [](void* ptr, float_t time) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->video_tick(time);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to tick, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to tick.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
_source_info.video_render = [](void* ptr, gs_effect_t* effect) {
|
|
|
|
try {
|
|
|
|
if (ptr)
|
|
|
|
reinterpret_cast<filter::shader::shader_instance*>(ptr)->video_render(effect);
|
|
|
|
} catch (std::exception& ex) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to render, error: %s", ex.what());
|
|
|
|
} catch (...) {
|
|
|
|
P_LOG_ERROR("<filter-shader> Failed to render.");
|
|
|
|
}
|
|
|
|
};
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
obs_register_source(&_source_info);
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
filter::shader::shader_factory::~shader_factory() {}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
filter::shader::shader_instance::shader_instance(obs_data_t* data, obs_source_t* self)
|
|
|
|
: _self(self), _active(true), _width(0), _height(0)
|
2019-08-07 15:15:46 +00:00
|
|
|
{
|
|
|
|
_fx = std::make_shared<gfx::effect_source::effect_source>();
|
2019-08-07 15:37:52 +00:00
|
|
|
_fx->set_valid_property_cb(std::bind(&filter::shader::shader_instance::valid_param, this, std::placeholders::_1));
|
|
|
|
_fx->set_override_cb(std::bind(&filter::shader::shader_instance::override_param, this, std::placeholders::_1));
|
|
|
|
|
2019-08-07 15:15:46 +00:00
|
|
|
_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
|
|
|
|
|
|
|
update(data);
|
|
|
|
}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
filter::shader::shader_instance::~shader_instance() {}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
uint32_t filter::shader::shader_instance::width()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
return _width;
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
uint32_t filter::shader::shader_instance::height()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
return _height;
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 15:37:52 +00:00
|
|
|
void filter::shader::shader_instance::properties(obs_properties_t* props)
|
|
|
|
{
|
2019-08-07 15:15:46 +00:00
|
|
|
_fx->properties(props);
|
|
|
|
}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 15:37:52 +00:00
|
|
|
void filter::shader::shader_instance::update(obs_data_t* data)
|
|
|
|
{
|
2019-08-07 15:15:46 +00:00
|
|
|
_fx->update(data);
|
|
|
|
}
|
2017-11-06 11:36:01 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_instance::activate()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
_active = true;
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_instance::deactivate()
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
_active = false;
|
2017-11-06 11:36:01 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 15:37:52 +00:00
|
|
|
bool filter::shader::shader_instance::valid_param(std::shared_ptr<gs::effect_parameter> param)
|
|
|
|
{
|
|
|
|
if (strcmpi(param->get_name().c_str(), "ImageSource"))
|
|
|
|
return false;
|
|
|
|
if (strcmpi(param->get_name().c_str(), "ImageSource_Size"))
|
|
|
|
return false;
|
|
|
|
if (strcmpi(param->get_name().c_str(), "ImageSource_Texel"))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void filter::shader::shader_instance::override_param(std::shared_ptr<gs::effect> effect)
|
|
|
|
{
|
|
|
|
auto p_source = effect->get_parameter("ImageSource");
|
|
|
|
auto p_source_size = effect->get_parameter("ImageSource_Size");
|
|
|
|
auto p_source_texel = effect->get_parameter("ImageSource_Texel");
|
|
|
|
|
|
|
|
if (p_source && (p_source->get_type() == gs::effect_parameter::type::Texture)) {
|
|
|
|
p_source->set_texture(_rt_tex);
|
|
|
|
}
|
|
|
|
if (p_source_size && (p_source_size->get_type() == gs::effect_parameter::type::Float2)) {
|
|
|
|
p_source_size->set_float2(_width, _height);
|
|
|
|
}
|
|
|
|
if (p_source_texel && (p_source_size->get_type() == gs::effect_parameter::type::Float2)) {
|
|
|
|
p_source_texel->set_float2(1.0f / _width, 1.0f / _height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_instance::video_tick(float_t sec_since_last)
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
obs_source_t* target = obs_filter_get_target(_self);
|
2018-01-19 19:45:49 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
{ // Update width and height.
|
|
|
|
_width = obs_source_get_base_width(target);
|
|
|
|
_height = obs_source_get_base_height(target);
|
2018-01-19 19:45:49 +00:00
|
|
|
}
|
2019-08-07 15:15:46 +00:00
|
|
|
|
|
|
|
_fx->tick(sec_since_last);
|
2019-08-07 15:37:52 +00:00
|
|
|
|
|
|
|
_rt_updated = false;
|
2018-04-09 11:28:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
void filter::shader::shader_instance::video_render(gs_effect_t* effect)
|
2018-11-07 14:24:25 +00:00
|
|
|
{
|
2019-08-07 10:43:23 +00:00
|
|
|
// Grab initial values.
|
|
|
|
obs_source_t* parent = obs_filter_get_parent(_self);
|
|
|
|
obs_source_t* target = obs_filter_get_target(_self);
|
|
|
|
gs_effect_t* effect_default = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT);
|
2018-04-29 01:07:26 +00:00
|
|
|
|
2019-08-07 10:43:23 +00:00
|
|
|
// Skip filter if anything is wrong.
|
2019-08-07 15:37:52 +00:00
|
|
|
if (!_active || !parent || !target || !_width || !_height || !effect_default) {
|
2019-08-07 10:43:23 +00:00
|
|
|
obs_source_skip_video_filter(_self);
|
|
|
|
return;
|
2018-04-29 01:07:26 +00:00
|
|
|
}
|
|
|
|
|
2019-08-07 15:37:52 +00:00
|
|
|
if (!_rt_updated) {
|
|
|
|
if (obs_source_process_filter_begin(_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) {
|
|
|
|
auto op = _rt->render(_width, _height);
|
|
|
|
gs_blend_state_push();
|
|
|
|
gs_reset_blend_state();
|
|
|
|
gs_set_cull_mode(GS_NEITHER);
|
|
|
|
gs_enable_color(true, true, true, true);
|
|
|
|
gs_enable_blending(false);
|
|
|
|
gs_enable_depth_test(false);
|
|
|
|
gs_enable_stencil_test(false);
|
|
|
|
gs_enable_stencil_write(false);
|
|
|
|
gs_ortho(0, static_cast<float_t>(_width), 0, static_cast<float_t>(_height), -1., 1.);
|
|
|
|
obs_source_process_filter_end(_self, effect_default, _width, _height);
|
|
|
|
gs_blend_state_pop();
|
|
|
|
}
|
|
|
|
_rt_tex = _rt->get_texture();
|
|
|
|
_rt_updated = true;
|
|
|
|
}
|
|
|
|
|
2019-08-07 15:15:46 +00:00
|
|
|
try {
|
|
|
|
_fx->render();
|
|
|
|
} catch (...) {
|
|
|
|
obs_source_skip_video_filter(_self);
|
|
|
|
}
|
2018-04-09 11:28:13 +00:00
|
|
|
}
|