obs-StreamFX/source/gfx/shader/gfx-shader.cpp
Michael Fabian 'Xaymar' Dirks 5a3954ae0e project: Fix License, License headers and Copyright information
Fixes several files incorrectly stated a different license from the actual project, as well as the copyright headers included in all files. This change has no effect on the licensing terms, it should clear up a bit of confusion by contributors. Plus the files get a bit smaller, and we have less duplicated information across the entire project.

Overall the project is GPLv2 if not built with Qt, and GPLv3 if it is built with Qt. There are no parts licensed under a different license, all have been adapted from other compatible licenses into GPLv2 or GPLv3.
2023-04-05 18:59:08 +02:00

651 lines
19 KiB
C++

// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2019-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// Copyright (C) 2021 coolsoft.rf <coolsoft.rf@gmail.com>
// Copyright (C) 2022 lainon <GermanAizek@yandex.ru>
// AUTOGENERATED COPYRIGHT HEADER END
#include "gfx-shader.hpp"
#include "obs/gs/gs-helper.hpp"
#include "obs/obs-tools.hpp"
#include "plugin.hpp"
#include "warning-disable.hpp"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include "warning-enable.hpp"
#define ST_I18N "Shader"
#define ST_I18N_REFRESH ST_I18N ".Refresh"
#define ST_KEY_REFRESH "Shader.Refresh"
#define ST_I18N_SHADER ST_I18N ".Shader"
#define ST_KEY_SHADER "Shader.Shader"
#define ST_I18N_SHADER_FILE ST_I18N_SHADER ".File"
#define ST_KEY_SHADER_FILE ST_KEY_SHADER ".File"
#define ST_I18N_SHADER_TECHNIQUE ST_I18N_SHADER ".Technique"
#define ST_KEY_SHADER_TECHNIQUE ST_KEY_SHADER ".Technique"
#define ST_I18N_SHADER_SIZE ST_I18N_SHADER ".Size"
#define ST_KEY_SHADER_SIZE ST_KEY_SHADER ".Size"
#define ST_I18N_SHADER_SIZE_WIDTH ST_I18N_SHADER_SIZE ".Width"
#define ST_KEY_SHADER_SIZE_WIDTH ST_KEY_SHADER_SIZE ".Width"
#define ST_I18N_SHADER_SIZE_HEIGHT ST_I18N_SHADER_SIZE ".Height"
#define ST_KEY_SHADER_SIZE_HEIGHT ST_KEY_SHADER_SIZE ".Height"
#define ST_I18N_SHADER_SEED ST_I18N_SHADER ".Seed"
#define ST_KEY_SHADER_SEED ST_KEY_SHADER ".Seed"
#define ST_I18N_PARAMETERS ST_I18N ".Parameters"
#define ST_KEY_PARAMETERS "Shader.Parameters"
streamfx::gfx::shader::shader::shader(obs_source_t* self, shader_mode mode)
: _self(self), _gfx_util(::streamfx::gfx::util::get()), _mode(mode), _base_width(1), _base_height(1), _active(true),
_shader(), _shader_file(), _shader_tech("Draw"), _shader_file_mt(), _shader_file_sz(), _shader_file_tick(0),
_width_type(size_type::Percent), _width_value(1.0), _height_type(size_type::Percent), _height_value(1.0),
_have_current_params(false), _time(0), _time_loop(0), _loops(0), _random(), _random_seed(0),
_rt_up_to_date(false), _rt(std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA_UNORM, GS_ZS_NONE))
{
// Initialize random values.
_random.seed(static_cast<unsigned long long>(_random_seed));
for (size_t idx = 0; idx < 16; idx++) {
_random_values[idx] =
static_cast<float_t>(static_cast<double_t>(_random()) / static_cast<double_t>(_random.max()));
}
}
streamfx::gfx::shader::shader::~shader() = default;
bool streamfx::gfx::shader::shader::is_shader_different(const std::filesystem::path& file)
{
try {
if (std::filesystem::exists(file)) {
// Check if the file name differs.
if (file != _shader_file)
return true;
}
if (std::filesystem::exists(_shader_file)) {
// Is the file write time different?
if (std::filesystem::last_write_time(_shader_file) != _shader_file_mt)
return true;
// Is the file size different?
if (std::filesystem::file_size(_shader_file) != _shader_file_sz)
return true;
}
return false;
} catch (const std::exception& ex) {
DLOG_ERROR("Loading shader '%s' failed with error: %s", file.c_str(), ex.what());
return false;
}
}
bool streamfx::gfx::shader::shader::is_technique_different(std::string_view tech)
{
// Is the technique different?
if (tech != _shader_tech)
return true;
return false;
}
bool streamfx::gfx::shader::shader::load_shader(const std::filesystem::path& file, std::string_view tech,
bool& shader_dirty, bool& param_dirty)
{
try {
if (!std::filesystem::exists(file))
return false;
shader_dirty = is_shader_different(file);
param_dirty = is_technique_different(tech) || shader_dirty;
// Update Shader
if (shader_dirty) {
_shader = streamfx::obs::gs::effect(file);
_shader_file_mt = std::filesystem::last_write_time(file);
_shader_file_sz = std::filesystem::file_size(file);
_shader_file = file;
_shader_file_tick = 0;
}
// Update Params
if (param_dirty) {
auto settings =
std::shared_ptr<obs_data_t>(obs_source_get_settings(_self), [](obs_data_t* p) { obs_data_release(p); });
bool have_valid_tech = false;
for (std::size_t idx = 0; idx < _shader.count_techniques(); idx++) {
if (_shader.get_technique(idx).name() == tech) {
have_valid_tech = true;
break;
}
}
if (have_valid_tech) {
_shader_tech = tech;
} else {
_shader_tech = _shader.get_technique(0).name();
// Update source data.
obs_data_set_string(settings.get(), ST_KEY_SHADER_TECHNIQUE, _shader_tech.c_str());
}
// Clear the shader parameters map and rebuild.
_shader_params.clear();
auto etech = _shader.get_technique(_shader_tech);
for (std::size_t idx = 0; idx < etech.count_passes(); idx++) {
auto pass = etech.get_pass(idx);
auto fetch_params = [&](std::size_t count,
std::function<streamfx::obs::gs::effect_parameter(std::size_t)> get_func) {
for (std::size_t vidx = 0; vidx < count; vidx++) {
auto el = get_func(vidx);
if (!el)
continue;
auto el_name = el.get_name();
auto fnd = _shader_params.find(el_name);
if (fnd != _shader_params.end())
continue;
auto param = streamfx::gfx::shader::parameter::make_parameter(this, el, ST_KEY_PARAMETERS);
if (param) {
_shader_params.insert_or_assign(el_name, param);
param->defaults(settings.get());
param->update(settings.get());
}
}
};
auto gvp = [&](std::size_t idx) { return pass.get_vertex_parameter(idx); };
fetch_params(pass.count_vertex_parameters(), gvp);
auto gpp = [&](std::size_t idx) { return pass.get_pixel_parameter(idx); };
fetch_params(pass.count_pixel_parameters(), gpp);
}
}
return true;
} catch (const std::exception& ex) {
DLOG_ERROR("Loading shader '%s' failed with error: %s", file.c_str(), ex.what());
return false;
} catch (...) {
return false;
}
}
void streamfx::gfx::shader::shader::defaults(obs_data_t* data)
{
obs_data_set_default_string(data, ST_KEY_SHADER_FILE, "");
obs_data_set_default_string(data, ST_KEY_SHADER_TECHNIQUE, "");
obs_data_set_default_string(data, ST_KEY_SHADER_SIZE_WIDTH, "100.0 %");
obs_data_set_default_string(data, ST_KEY_SHADER_SIZE_HEIGHT, "100.0 %");
obs_data_set_default_int(data, ST_KEY_SHADER_SEED, static_cast<long long>(time(NULL)));
}
void streamfx::gfx::shader::shader::properties(obs_properties_t* pr)
{
_have_current_params = false;
{
auto grp = obs_properties_create();
obs_properties_add_group(pr, ST_KEY_SHADER, D_TRANSLATE(ST_I18N_SHADER), OBS_GROUP_NORMAL, grp);
{
std::string path = "";
if (_shader_file.has_parent_path()) {
path = _shader_file.parent_path().string();
} else {
path = streamfx::data_file_path("examples/").u8string();
}
auto p = obs_properties_add_path(grp, ST_KEY_SHADER_FILE, D_TRANSLATE(ST_I18N_SHADER_FILE), OBS_PATH_FILE,
"*.*", path.c_str());
}
{
auto p = obs_properties_add_list(grp, ST_KEY_SHADER_TECHNIQUE, D_TRANSLATE(ST_I18N_SHADER_TECHNIQUE),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
}
{
obs_properties_add_button2(
grp, ST_KEY_REFRESH, D_TRANSLATE(ST_I18N_REFRESH),
[](obs_properties_t* props, obs_property_t* prop, void* priv) {
return reinterpret_cast<streamfx::gfx::shader::shader*>(priv)->on_refresh_properties(props, prop);
},
this);
}
if (_mode != shader_mode::Transition) {
auto grp2 = obs_properties_create();
obs_properties_add_group(grp, ST_KEY_SHADER_SIZE, D_TRANSLATE(ST_I18N_SHADER_SIZE), OBS_GROUP_NORMAL, grp2);
{
auto p = obs_properties_add_text(grp2, ST_KEY_SHADER_SIZE_WIDTH, D_TRANSLATE(ST_I18N_SHADER_SIZE_WIDTH),
OBS_TEXT_DEFAULT);
}
{
auto p = obs_properties_add_text(grp2, ST_KEY_SHADER_SIZE_HEIGHT,
D_TRANSLATE(ST_I18N_SHADER_SIZE_HEIGHT), OBS_TEXT_DEFAULT);
}
}
{
auto p = obs_properties_add_int_slider(grp, ST_KEY_SHADER_SEED, D_TRANSLATE(ST_I18N_SHADER_SEED),
std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 1);
}
}
{
auto grp = obs_properties_create();
obs_properties_add_group(pr, ST_KEY_PARAMETERS, D_TRANSLATE(ST_I18N_PARAMETERS), OBS_GROUP_NORMAL, grp);
}
// Manually call the refresh.
on_refresh_properties(pr, nullptr);
}
bool streamfx::gfx::shader::shader::on_refresh_properties(obs_properties_t* props, obs_property_t* prop)
{
if (_shader) { // Clear list of techniques and rebuild it.
obs_property_t* p_tech_list = obs_properties_get(props, ST_KEY_SHADER_TECHNIQUE);
obs_property_list_clear(p_tech_list);
for (std::size_t idx = 0; idx < _shader.count_techniques(); idx++) {
auto tech = _shader.get_technique(idx);
obs_property_list_add_string(p_tech_list, tech.name().c_str(), tech.name().c_str());
}
}
{ // Clear parameter options.
auto grp = obs_property_group_content(obs_properties_get(props, ST_KEY_PARAMETERS));
for (auto p = obs_properties_first(grp); p != nullptr; p = obs_properties_first(grp)) {
obs_properties_remove_by_name(grp, obs_property_name(p));
}
// Rebuild new parameters.
obs_data_t* data = obs_source_get_settings(_self);
for (auto kv : _shader_params) {
try {
kv.second->defaults(data);
kv.second->update(data);
kv.second->properties(grp, data);
} catch (...) {
// ToDo: Do something with these?
}
}
//obs_source_update(_self, data);
}
return true;
}
bool streamfx::gfx::shader::shader::on_shader_or_technique_modified(obs_properties_t* props, obs_property_t* prop,
obs_data_t* data)
{
bool shader_dirty = false;
bool param_dirty = false;
if (!update_shader(data, shader_dirty, param_dirty))
return false;
{ // Clear list of techniques and rebuild it.
obs_property_t* p_tech_list = obs_properties_get(props, ST_KEY_SHADER_TECHNIQUE);
obs_property_list_clear(p_tech_list);
for (std::size_t idx = 0; idx < _shader.count_techniques(); idx++) {
auto tech = _shader.get_technique(idx);
obs_property_list_add_string(p_tech_list, tech.name().c_str(), tech.name().c_str());
}
}
if (param_dirty || !_have_current_params) {
// Clear parameter options.
auto grp = obs_property_group_content(obs_properties_get(props, ST_KEY_PARAMETERS));
for (auto p = obs_properties_first(grp); p != nullptr; p = obs_properties_first(grp)) {
obs_properties_remove_by_name(grp, obs_property_name(p));
}
// Rebuild new parameters.
for (auto kv : _shader_params) {
kv.second->properties(grp, data);
kv.second->defaults(data);
kv.second->update(data);
}
}
_have_current_params = true;
return shader_dirty || param_dirty || !_have_current_params;
}
bool streamfx::gfx::shader::shader::update_shader(obs_data_t* data, bool& shader_dirty, bool& param_dirty)
{
const char* file_c = obs_data_get_string(data, ST_KEY_SHADER_FILE);
std::string file = file_c ? file_c : "";
const char* tech_c = obs_data_get_string(data, ST_KEY_SHADER_TECHNIQUE);
std::string tech = tech_c ? tech_c : "Draw";
return load_shader(file, tech, shader_dirty, param_dirty);
}
inline std::pair<streamfx::gfx::shader::size_type, double_t> parse_text_as_size(const char* text)
{
double_t v = 0;
if (sscanf(text, "%lf", &v) == 1) {
const char* prc_chr = strrchr(text, '%');
if (prc_chr && (*prc_chr == '%')) {
return {streamfx::gfx::shader::size_type::Percent, v / 100.0};
} else {
return {streamfx::gfx::shader::size_type::Pixel, v};
}
} else {
return {streamfx::gfx::shader::size_type::Percent, 1.0};
}
}
void streamfx::gfx::shader::shader::update(obs_data_t* data)
{
bool v1, v2;
update_shader(data, v1, v2);
{
auto sz_x = parse_text_as_size(obs_data_get_string(data, ST_KEY_SHADER_SIZE_WIDTH));
_width_type = sz_x.first;
_width_value = std::clamp(sz_x.second, 0.01, 8192.0);
auto sz_y = parse_text_as_size(obs_data_get_string(data, ST_KEY_SHADER_SIZE_HEIGHT));
_height_type = sz_y.first;
_height_value = std::clamp(sz_y.second, 0.01, 8192.0);
}
if (int32_t seed = static_cast<int32_t>(obs_data_get_int(data, ST_KEY_SHADER_SEED)); _random_seed != seed) {
_random_seed = seed;
_random.seed(static_cast<unsigned long long>(_random_seed));
for (size_t idx = 0; idx < 16; idx++) {
_random_values[idx] =
static_cast<float_t>(static_cast<double_t>(_random()) / static_cast<double_t>(_random.max()));
}
}
for (auto kv : _shader_params) {
kv.second->defaults(data);
kv.second->update(data);
}
}
uint32_t streamfx::gfx::shader::shader::width()
{
switch (_mode) {
case shader_mode::Transition:
return _base_width;
case shader_mode::Source:
case shader_mode::Filter:
switch (_width_type) {
case size_type::Pixel:
return std::clamp(static_cast<uint32_t>(_width_value), 1u, 16384u);
case size_type::Percent:
return std::clamp(static_cast<uint32_t>(_width_value * _base_width), 1u, 16384u);
}
default:
return 0;
}
}
uint32_t streamfx::gfx::shader::shader::height()
{
switch (_mode) {
case shader_mode::Transition:
return _base_height;
case shader_mode::Source:
case shader_mode::Filter:
switch (_height_type) {
case size_type::Pixel:
return std::clamp(static_cast<uint32_t>(_height_value), 1u, 16384u);
case size_type::Percent:
return std::clamp(static_cast<uint32_t>(_height_value * _base_height), 1u, 16384u);
}
default:
return 0;
}
}
uint32_t streamfx::gfx::shader::shader::base_width()
{
return _base_width;
}
uint32_t streamfx::gfx::shader::shader::base_height()
{
return _base_height;
}
bool streamfx::gfx::shader::shader::tick(float_t time)
{
_shader_file_tick = static_cast<float_t>(static_cast<double_t>(_shader_file_tick) + static_cast<double_t>(time));
if (_shader_file_tick >= 1.0f / 3.0f) {
_shader_file_tick -= 1.0f / 3.0f;
bool v1, v2;
load_shader(_shader_file, _shader_tech, v1, v2);
}
// Update State
_time += time;
_time_loop += time;
if (_time_loop > 1.) {
_time_loop -= 1.;
// Loops
_loops += 1;
if (_loops >= 4194304)
_loops = -_loops;
}
// Recreate Per-Activation-Random values.
for (size_t idx = 0; idx < 8; idx++) {
_random_values[8 + idx] =
static_cast<float_t>(static_cast<double_t>(_random()) / static_cast<double_t>(_random.max()));
}
// Flag Render Target as outdated.
_rt_up_to_date = false;
return false;
}
void streamfx::gfx::shader::shader::prepare_render()
{
if (!_shader)
return;
// Assign user parameters
for (auto kv : _shader_params) {
kv.second->assign();
}
// float4 Time: (Time in Seconds), (Time in Current Second), (Time in Seconds only), (Random Value)
if (streamfx::obs::gs::effect_parameter el = _shader.get_parameter("Time"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Float4) {
el.set_float4(
_time, _time_loop, static_cast<float_t>(_loops),
static_cast<float_t>(static_cast<double_t>(_random()) / static_cast<double_t>(_random.max())));
}
}
// float4 ViewSize: (Width), (Height), (1.0 / Width), (1.0 / Height)
if (auto el = _shader.get_parameter("ViewSize"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Float4) {
el.set_float4(static_cast<float_t>(width()), static_cast<float_t>(height()),
1.0f / static_cast<float_t>(width()), 1.0f / static_cast<float_t>(height()));
}
}
// float4x4 Random: float4[Per-Instance Random], float4[Per-Activation Random], float4x2[Per-Frame Random]
if (auto el = _shader.get_parameter("Random"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Matrix) {
el.set_value(_random_values, 16);
}
}
// int32 RandomSeed: Seed used for random generation
if (auto el = _shader.get_parameter("RandomSeed"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Integer) {
el.set_int(_random_seed);
}
}
return;
}
void streamfx::gfx::shader::shader::render(gs_effect* effect)
{
if (!_shader)
return;
if (!effect)
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
if (!_rt_up_to_date) {
#ifdef ENABLE_PROFILING
::streamfx::obs::gs::debug_marker profiler1{::streamfx::obs::gs::debug_color_cache, "Render Cache"};
#endif
auto op = _rt->render(width(), height());
vec4 zero = {0, 0, 0, 0};
gs_clear(GS_CLEAR_COLOR, &zero, 0, 0);
gs_ortho(0, 1, 0, 1, 0, 1);
// Update Blend State
gs_blend_state_push();
gs_reset_blend_state();
gs_enable_blending(false);
gs_blend_function_separate(GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_ZERO);
gs_enable_color(true, true, true, true);
// Fix sRGB Status
bool old_srgb = gs_framebuffer_srgb_enabled();
gs_enable_framebuffer_srgb(false);
while (gs_effect_loop(_shader.get_object(), _shader_tech.c_str())) {
_gfx_util->draw_fullscreen_triangle();
}
// Restore sRGB Status
gs_enable_framebuffer_srgb(old_srgb);
// Restore Blend State
gs_blend_state_pop();
_rt_up_to_date = true;
}
if (auto tex = _rt->get_texture(); tex) {
#ifdef ENABLE_PROFILING
::streamfx::obs::gs::debug_marker profiler1{::streamfx::obs::gs::debug_color_render, "Draw Cache"};
#endif
gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), tex->get_object());
while (gs_effect_loop(effect, "Draw")) {
gs_draw_sprite(nullptr, 0, width(), height());
}
}
}
void streamfx::gfx::shader::shader::set_size(uint32_t w, uint32_t h)
{
_base_width = w;
_base_height = h;
}
void streamfx::gfx::shader::shader::set_input_a(std::shared_ptr<streamfx::obs::gs::texture> tex, bool srgb)
{
if (!_shader)
return;
std::string_view params[] = {
"InputA",
"image",
"tex_a",
};
for (auto& name : params) {
if (streamfx::obs::gs::effect_parameter el = _shader.get_parameter(name.data()); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Texture) {
el.set_texture(tex, srgb);
break;
}
}
}
}
void streamfx::gfx::shader::shader::set_input_b(std::shared_ptr<streamfx::obs::gs::texture> tex, bool srgb)
{
if (!_shader)
return;
std::string_view params[] = {
"InputB",
"image2",
"tex_b",
};
for (auto& name : params) {
if (streamfx::obs::gs::effect_parameter el = _shader.get_parameter(name.data()); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Texture) {
el.set_texture(tex, srgb);
break;
}
}
}
}
void streamfx::gfx::shader::shader::set_transition_time(float_t t)
{
if (!_shader)
return;
if (streamfx::obs::gs::effect_parameter el = _shader.get_parameter("TransitionTime"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Float) {
el.set_float(t);
}
}
}
void streamfx::gfx::shader::shader::set_transition_size(uint32_t w, uint32_t h)
{
if (!_shader)
return;
if (streamfx::obs::gs::effect_parameter el = _shader.get_parameter("TransitionSize"); el != nullptr) {
if (el.get_type() == streamfx::obs::gs::effect_parameter::type::Integer2) {
el.set_int2(static_cast<int32_t>(w), static_cast<int32_t>(h));
}
}
}
void streamfx::gfx::shader::shader::set_visible(bool visible)
{
_visible = visible;
for (auto kv : _shader_params) {
kv.second->visible(visible);
}
}
void streamfx::gfx::shader::shader::set_active(bool active)
{
_active = active;
for (auto kv : _shader_params) {
kv.second->active(active);
}
// Recreate Per-Activation-Random values.
for (size_t idx = 0; idx < 4; idx++) {
_random_values[4 + idx] =
static_cast<float_t>(static_cast<double_t>(_random()) / static_cast<double_t>(_random.max()));
}
}
obs_source_t* streamfx::gfx::shader::shader::get()
{
return _self;
}
std::filesystem::path streamfx::gfx::shader::shader::get_shader_file()
{
return _shader_file;
}