From 4601df76d904f8503c9f2652f5219a49bf8724b5 Mon Sep 17 00:00:00 2001 From: "coolsoft.rf" Date: Thu, 25 Nov 2021 21:11:59 +0100 Subject: [PATCH] gfx/shader/param/texture: Add support for Texture parameters Implements File, Source and Enumeration type for Texture shader inputs, completing the initial Shader implementation. Related: #5 Co-authored-by: Michael Fabian 'Xaymar' Dirks --- data/locale/en-US.ini | 5 + .../gfx/shader/gfx-shader-param-texture.cpp | 371 ++++++++++++++++++ .../gfx/shader/gfx-shader-param-texture.hpp | 84 ++++ source/gfx/shader/gfx-shader-param.cpp | 3 + 4 files changed, 463 insertions(+) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 95e94daa..5cef06d0 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -238,6 +238,11 @@ Shader.Shader.Size.Width="Width" Shader.Shader.Size.Height="Height" Shader.Shader.Seed="Randomization Seed" Shader.Parameters="Shader Parameters" +Shader.Parameter.Texture.Type="Type" +Shader.Parameter.Texture.Type.File="File" +Shader.Parameter.Texture.Type.Source="Source" +Shader.Parameter.Texture.File="File" +Shader.Parameter.Texture.Source="Source" Filter.Shader="Shader" Source.Shader="Shader" Transition.Shader="Shader" diff --git a/source/gfx/shader/gfx-shader-param-texture.cpp b/source/gfx/shader/gfx-shader-param-texture.cpp index e69de29b..3933443c 100644 --- a/source/gfx/shader/gfx-shader-param-texture.cpp +++ b/source/gfx/shader/gfx-shader-param-texture.cpp @@ -0,0 +1,371 @@ +// Modern effects for a modern Streamer +// Copyright (C) 2019 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-shader-param-texture.hpp" +#include "strings.hpp" +#include +#include +#include +#include "gfx-shader.hpp" +#include "gfx/gfx-debug.hpp" +#include "obs/gs/gs-helper.hpp" +#include "obs/obs-source-tracker.hpp" + +// TODO: +// - FFT Audio Conversion +// - FFT Variable Size... + +// UI: +// Name/Key { +// Type = File/Source/Sink +// File = ... +// Source = ... +// } + +#define ST_I18N "Shader.Parameter.Texture" +#define ST_KEY_TYPE ".Type" +#define ST_I18N_TYPE ST_I18N ".Type" +#define ST_I18N_TYPE_FILE ST_I18N_TYPE ".File" +#define ST_I18N_TYPE_SOURCE ST_I18N_TYPE ".Source" +#define ST_KEY_FILE ".File" +#define ST_I18N_FILE ST_I18N ".File" +#define ST_KEY_SOURCE ".Source" +#define ST_I18N_SOURCE ST_I18N ".Source" + +static constexpr std::string_view _annotation_field_type = "field_type"; +static constexpr std::string_view _annotation_enum_entry = "enum_%zu"; +static constexpr std::string_view _annotation_enum_entry_name = "enum_%zu_name"; + +streamfx::gfx::shader::texture_field_type streamfx::gfx::shader::get_texture_field_type_from_string(std::string v) +{ + std::map matches = { + {"input", texture_field_type::Input}, + {"enum", texture_field_type::Enum}, + {"enumeration", texture_field_type::Enum}, + }; + + auto fnd = matches.find(v); + if (fnd != matches.end()) + return fnd->second; + + return texture_field_type::Input; +} + +streamfx::gfx::shader::texture_parameter::texture_parameter(streamfx::gfx::shader::shader* parent, + streamfx::obs::gs::effect_parameter param, + std::string prefix) + : parameter(parent, param, prefix) +{ + char string_buffer[256]; + + // Build keys and names. + { + _keys.reserve(3); + { // Type + snprintf(string_buffer, sizeof(string_buffer), "%s%s", get_key().data(), ST_KEY_TYPE); + _keys.push_back(std::string(string_buffer)); + } + { // File + snprintf(string_buffer, sizeof(string_buffer), "%s%s", get_key().data(), ST_KEY_FILE); + _keys.push_back(std::string(string_buffer)); + } + { // Source + snprintf(string_buffer, sizeof(string_buffer), "%s%s", get_key().data(), ST_KEY_SOURCE); + _keys.push_back(std::string(string_buffer)); + } + } + + // Detect Field Types + if (auto anno = get_parameter().get_annotation(_annotation_field_type); anno) { + _field_type = get_texture_field_type_from_string(anno.get_default_string()); + } + + if (field_type() == texture_field_type::Enum) { + for (std::size_t idx = 0; idx < std::numeric_limits::max(); idx++) { + // Build key. + std::string key_name; + std::string key_value; + { + snprintf(string_buffer, sizeof(string_buffer), _annotation_enum_entry.data(), idx); + key_value = std::string(string_buffer); + snprintf(string_buffer, sizeof(string_buffer), _annotation_enum_entry_name.data(), idx); + key_name = std::string(string_buffer); + } + + // Value must be given, name is optional. + if (auto eanno = get_parameter().get_annotation(key_value); + eanno && (get_type_from_effect_type(eanno.get_type()) == get_type())) { + texture_enum_data entry; + + entry.data.file = std::filesystem::path(eanno.get_default_string()); + + if (auto nanno = get_parameter().get_annotation(key_name); + nanno && (nanno.get_type() == streamfx::obs::gs::effect_parameter::type::String)) { + entry.name = nanno.get_default_string(); + } else { + entry.name = "Unnamed Entry"; + } + + _values.push_back(entry); + } else { + break; + } + } + + if (_values.size() == 0) { + _field_type = texture_field_type::Input; + } + } else { + } +} + +streamfx::gfx::shader::texture_parameter::~texture_parameter() {} + +bool streamfx::gfx::shader::texture_parameter::modified_type(void* priv, obs_properties_t* props, obs_property_t*, + obs_data_t* settings) +{ + auto self = reinterpret_cast(priv); + if (self->field_type() == texture_field_type::Input) { + auto type = static_cast(obs_data_get_int(settings, self->_keys[0].c_str())); + obs_property_set_visible(obs_properties_get(props, self->_keys[1].c_str()), type == texture_type::File); + obs_property_set_visible(obs_properties_get(props, self->_keys[2].c_str()), type == texture_type::Source); + return true; + } + return false; +} + +void streamfx::gfx::shader::texture_parameter::properties(obs_properties_t* props, obs_data_t* settings) +{ + if (!is_visible()) + return; + + if (field_type() == texture_field_type::Enum) { + auto p = obs_properties_add_list(props, get_key().data(), has_name() ? get_name().data() : get_key().data(), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + for (auto v : _values) { + obs_property_list_add_string(p, v.name.c_str(), v.data.file.generic_u8string().c_str()); + } + } else { + obs_properties_t* pr = obs_properties_create(); + { + auto p = obs_properties_add_group(props, get_key().data(), + has_name() ? get_name().data() : get_key().data(), OBS_GROUP_NORMAL, pr); + if (has_description()) + obs_property_set_long_description(p, get_description().data()); + } + + { + auto p = obs_properties_add_list(pr, _keys[0].c_str(), D_TRANSLATE(ST_I18N_TYPE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_modified_callback2(p, modified_type, this); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_TYPE_FILE), static_cast(texture_type::File)); + obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_TYPE_SOURCE), static_cast(texture_type::Source)); + modified_type(this, props, p, settings); + } + + { + // ToDo: Filter and Default Path. + auto p = obs_properties_add_path(pr, _keys[1].c_str(), D_TRANSLATE(ST_I18N_FILE), OBS_PATH_FILE, "* (*.*)", + nullptr); + } + + { + auto p = obs_properties_add_list(pr, _keys[2].c_str(), D_TRANSLATE(ST_I18N_SOURCE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, "", ""); + obs::source_tracker::get()->enumerate( + [&p](std::string name, obs_source_t*) { + std::stringstream sstr; + sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SOURCE) << ")"; + obs_property_list_add_string(p, sstr.str().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*) { + std::stringstream sstr; + sstr << name << " (" << D_TRANSLATE(S_SOURCETYPE_SCENE) << ")"; + obs_property_list_add_string(p, sstr.str().c_str(), name.c_str()); + return false; + }, + obs::source_tracker::filter_scenes); + } + } +} + +std::filesystem::path make_absolute_to(std::filesystem::path origin, std::filesystem::path destination) +{ + auto destination_dir = std::filesystem::absolute(destination.remove_filename()); + return std::filesystem::absolute(origin / destination); +} + +void streamfx::gfx::shader::texture_parameter::update(obs_data_t* settings) +{ + // Value is assigned elsewhere. + if (is_automatic()) + return; + + try { + if (field_type() == texture_field_type::Enum) { + std::filesystem::path file_path = obs_data_get_string(settings, get_key().data()); + if (file_path.is_relative()) { + file_path = make_absolute_to(file_path, get_parent()->get_shader_file()); + } + + if ((file_path != _file_path) || !_file_texture) { + auto file_texture = std::make_shared(file_path.generic_u8string().c_str()); + _file_texture = file_texture; + _file_path = file_path; + } + } else { + auto type = static_cast(obs_data_get_int(settings, _keys[0].c_str())); + if (type == texture_type::File) { + std::filesystem::path file_path = obs_data_get_string(settings, _keys[1].c_str()); + if (file_path.is_relative()) { + file_path = make_absolute_to(file_path, get_parent()->get_shader_file()); + } + + if ((file_path != _file_path) || !_file_texture) { + auto file_texture = + std::make_shared(file_path.generic_u8string().c_str()); + _file_texture = file_texture; + _file_path = file_path; + } + } else { + auto source_name = obs_data_get_string(settings, _keys[2].c_str()); + + // Try and grab the source itself. + auto source = std::shared_ptr(obs_get_source_by_name(source_name), + [](obs_source_t* v) { obs_source_release(v); }); + if (!source) { + throw std::runtime_error("Specified Source does not exist."); + } + + // Attach the child to our parent. + auto child = std::make_shared(get_parent()->get(), source); + + // Create necessary visible and active objects. + std::shared_ptr active; + std::shared_ptr visible; + if (_active) { + active = std::make_shared(source.get()); + } + if (_visible) { + visible = std::make_shared(source.get()); + } + + // Create the necessary render target to capture the source. + auto rt = std::make_shared(GS_RGBA, GS_ZS_NONE); + + // Propagate all of this into the storage. + _source_rendertarget = rt; + _source_visible = visible; + _source_active = active; + _source_child = child; + _source = source; + } + + if (type != _type) { + if (_type == texture_type::Source) { + _source.reset(); + _source_child.reset(); + _source_active.reset(); + _source_visible.reset(); + _source_rendertarget.reset(); + } else if (_type == texture_type::File) { + _file_texture.reset(); + } + _type = type; + } + } + } catch (...) { + // ToDo: Determine what to do with these. + } +} + +void streamfx::gfx::shader::texture_parameter::assign() +{ + if (is_automatic()) + return; + + // If this is a source and active or visible, capture it. + if ((_type == texture_type::Source) && (_active || _visible) && _source_rendertarget) { +#ifdef ENABLE_PROFILING + ::streamfx::obs::gs::debug_marker profiler1{::streamfx::obs::gs::debug_color_capture, "Parameter '%s'", + get_key().data()}; + ::streamfx::obs::gs::debug_marker profiler2{::streamfx::obs::gs::debug_color_capture, "Capture '%s'", + obs_source_get_name(_source.get())}; +#endif + uint32_t width = obs_source_get_width(_source.get()); + uint32_t height = obs_source_get_height(_source.get()); + + auto op = _source_rendertarget->render(width, height); + + gs_matrix_push(); + gs_ortho(0, static_cast(width), 0, static_cast(height), 0, 1); + + // ToDo: Figure out if this breaks some sources. + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + gs_enable_blending(false); + + gs_enable_color(true, true, true, true); + + obs_source_video_render(_source.get()); + + gs_blend_state_pop(); + gs_matrix_pop(); + } + + if (_type == texture_type::Source) { + if (_source_rendertarget) { + auto tex = _source_rendertarget->get_texture(); + if (tex) { + get_parameter().set_texture(_source_rendertarget->get_texture(), false); + } + } + } else if (_type == texture_type::File) { + if (_file_texture) { + // Loaded files are always linear. + get_parameter().set_texture(_file_texture, false); + } + } +} + +void streamfx::gfx::shader::texture_parameter::visible(bool visible) +{ + _visible = visible; + if (visible) { + if (_source) { + _source_visible = std::make_shared(_source.get()); + } + } else { + _source_visible.reset(); + } +} + +void streamfx::gfx::shader::texture_parameter::active(bool active) +{ + _active = active; + if (active) { + if (_source) { + _source_active = std::make_shared(_source.get()); + } + } else { + _source_active.reset(); + } +} diff --git a/source/gfx/shader/gfx-shader-param-texture.hpp b/source/gfx/shader/gfx-shader-param-texture.hpp index e69de29b..d44e5d48 100644 --- a/source/gfx/shader/gfx-shader-param-texture.hpp +++ b/source/gfx/shader/gfx-shader-param-texture.hpp @@ -0,0 +1,84 @@ + +#pragma once +#include +#include +#include "gfx-shader-param.hpp" +#include "obs/gs/gs-rendertarget.hpp" +#include "obs/gs/gs-texture.hpp" +#include "obs/obs-tools.hpp" + +namespace streamfx::gfx { + namespace shader { + enum class texture_field_type { + Input, + Enum, + }; + + texture_field_type get_texture_field_type_from_string(std::string v); + + enum class texture_type { + File, + Source, + }; + + struct texture_data { + std::filesystem::path file; + }; + + struct texture_enum_data { + std::string name; + texture_data data; + }; + + struct texture_parameter : public parameter { + // Descriptor + texture_field_type _field_type; + std::vector _keys; + + // Enumeration Information + std::list _values; + + // Data + texture_type _type; + bool _active; + bool _visible; + + // Data: File + std::filesystem::path _file_path; + std::shared_ptr _file_texture; + + // Data: Source + std::string _source_name; + std::shared_ptr _source; + std::shared_ptr _source_child; + std::shared_ptr _source_active; + std::shared_ptr _source_visible; + std::shared_ptr _source_rendertarget; + + public: + texture_parameter(streamfx::gfx::shader::shader* parent, streamfx::obs::gs::effect_parameter param, + std::string prefix); + virtual ~texture_parameter(); + + void defaults(obs_data_t* settings) override{}; + + static bool modified_type(void*, obs_properties_t*, obs_property_t*, obs_data_t*); + + void properties(obs_properties_t* props, obs_data_t* settings) override; + + void update(obs_data_t* settings) override; + + void assign() override; + + void visible(bool visible) override; + + void active(bool enabled) override; + + public: + inline texture_field_type field_type() + { + return _field_type; + } + }; + } // namespace shader +} // namespace streamfx::gfx diff --git a/source/gfx/shader/gfx-shader-param.cpp b/source/gfx/shader/gfx-shader-param.cpp index 54dba13d..ef4fcb5b 100644 --- a/source/gfx/shader/gfx-shader-param.cpp +++ b/source/gfx/shader/gfx-shader-param.cpp @@ -19,6 +19,7 @@ #include #include #include "gfx-shader-param-basic.hpp" +#include "gfx-shader-param-texture.hpp" #define ST_ANNO_ORDER "order" #define ST_ANNO_VISIBILITY "visible" @@ -203,6 +204,8 @@ std::shared_ptr return std::make_shared(parent, param, prefix); case parameter_type::Float: return std::make_shared(parent, param, prefix); + case parameter_type::Texture: + return std::make_shared(parent, param, prefix); default: return nullptr; }