From 26b447901cc056deab01d708257f4c1a227dc6f4 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 6 Nov 2017 12:36:01 +0100 Subject: [PATCH 01/12] filter-custom-shader: Implement new Filter 'Custom Shader' Custom Shader allows you to write your own effect files and just have them applied to your source(s). It will dynamically update the properties to match the parameters in the source as well as offer some special parameters to the shader. # Conflicts: # CMakeLists.txt # data/locale/en-US.ini # Conflicts: # data/locale/en-US.ini --- CMakeLists.txt | 2 + data/locale/en-US.ini | 8 + source/filter-custom-shader.cpp | 409 ++++++++++++++++++++++++++++++++ source/filter-custom-shader.h | 111 +++++++++ 4 files changed, 530 insertions(+) create mode 100644 source/filter-custom-shader.cpp create mode 100644 source/filter-custom-shader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3474f7c7..f65534bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ SET(obs-stream-effects_HEADERS "${PROJECT_SOURCE_DIR}/source/filter-blur.h" "${PROJECT_SOURCE_DIR}/source/filter-shape.h" "${PROJECT_SOURCE_DIR}/source/filter-transform.h" + "${PROJECT_SOURCE_DIR}/source/filter-custom-shader.h" "${PROJECT_SOURCE_DIR}/source/source-mirror.h" "${PROJECT_SOURCE_DIR}/source/gs-helper.h" "${PROJECT_SOURCE_DIR}/source/gs-effect.h" @@ -58,6 +59,7 @@ SET(obs-stream-effects_SOURCES "${PROJECT_SOURCE_DIR}/source/filter-blur.cpp" "${PROJECT_SOURCE_DIR}/source/filter-shape.cpp" "${PROJECT_SOURCE_DIR}/source/filter-transform.cpp" + "${PROJECT_SOURCE_DIR}/source/filter-custom-shader.cpp" "${PROJECT_SOURCE_DIR}/source/source-mirror.cpp" "${PROJECT_SOURCE_DIR}/source/gs-helper.cpp" "${PROJECT_SOURCE_DIR}/source/gs-effect.cpp" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index d912413c..b90e65e9 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -14,6 +14,14 @@ Filter.Blur.Bilateral.Smoothing="Smoothing" Filter.Blur.Bilateral.Sharpness="Sharpness" Filter.Blur.ColorFormat="Color Format" +# Filter - Custom Shader +Filter.CustomShader="Custom Shader" +Filter.CustomShader.Type="Type" +Filter.CustomShader.Type.Text="Text Input" +Filter.CustomShader.Type.File="File Input" +Filter.CustomShader.Content.Text="Effect Text" +Filter.CustomShader.Content.File="Effect File" + # Filter - Displacement Filter.Displacement="Displacement Mapping" Filter.Displacement.File="File" diff --git a/source/filter-custom-shader.cpp b/source/filter-custom-shader.cpp new file mode 100644 index 00000000..453e7e94 --- /dev/null +++ b/source/filter-custom-shader.cpp @@ -0,0 +1,409 @@ +/* + * 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 + */ + +#include "filter-custom-shader.h" +#include "strings.h" +#include +#include + +extern "C" { +#pragma warning (push) +#pragma warning (disable: 4201) +#include "libobs/util/platform.h" +#include "libobs/graphics/graphics.h" +#include "libobs/graphics/matrix4.h" +#pragma warning (pop) +} + +#define S "Filter.CustomShader" +#define S_TYPE "Filter.CustomShader.Type" +#define S_TYPE_TEXT "Filter.CustomShader.Type.Text" +#define S_TYPE_FILE "Filter.CustomShader.Type.File" +#define S_CONTENT_TEXT "Filter.CustomShader.Content.Text" +#define S_CONTENT_FILE "Filter.CustomShader.Content.File" + +#define DEFAULT_SHADER "" + +/* Special Parameters + +Default: +- ViewSize - View Resolution, float2 +- ViewSizeI - View Resolution, integer2 +- Pass - Current Pass Index, integer + +Texture2d: +- $(param)_size - Texture Size, float2 +- $(param)_isize - Texture Size, integer2 +- $(param)_texel - Texture Texel Size, float2 + +*/ + +enum class ShaderType : int64_t { + Text, + File +}; + +static Filter::CustomShader* handler; +INITIALIZER(HandlerInit) { + initializerFunctions.push_back([] { + handler = new Filter::CustomShader(); + }); + finalizerFunctions.push_back([] { + delete handler; + }); +} + +Filter::CustomShader::CustomShader() { + memset(&sourceInfo, 0, sizeof(obs_source_info)); + sourceInfo.id = "obs-stream-effects-filter-custom-shader"; + sourceInfo.type = OBS_SOURCE_TYPE_FILTER; + sourceInfo.output_flags = OBS_SOURCE_VIDEO; + sourceInfo.get_name = get_name; + sourceInfo.get_defaults = get_defaults; + sourceInfo.get_properties = get_properties; + + sourceInfo.create = create; + sourceInfo.destroy = destroy; + sourceInfo.update = update; + sourceInfo.activate = activate; + sourceInfo.deactivate = deactivate; + sourceInfo.video_tick = video_tick; + sourceInfo.video_render = video_render; + + obs_register_source(&sourceInfo); +} + +Filter::CustomShader::~CustomShader() {} + +const char * Filter::CustomShader::get_name(void *) { + return P_TRANSLATE(S); +} + +void Filter::CustomShader::get_defaults(obs_data_t *data) { + obs_data_set_default_int(data, S_TYPE, (int64_t)ShaderType::Text); + obs_data_set_default_string(data, S_CONTENT_TEXT, DEFAULT_SHADER); +} + +obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { + obs_properties_t *pr = obs_properties_create(); + obs_property_t *p; + + p = obs_properties_add_list(pr, S_TYPE, P_TRANSLATE(S_TYPE), obs_combo_type::OBS_COMBO_TYPE_LIST, + obs_combo_format::OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(p, P_TRANSLATE(S_TYPE_TEXT), (int64_t)ShaderType::Text); + obs_property_list_add_int(p, P_TRANSLATE(S_TYPE_FILE), (int64_t)ShaderType::File); + obs_property_set_modified_callback(p, modified_properties); + + p = obs_properties_add_text(pr, S_CONTENT_TEXT, P_TRANSLATE(S_CONTENT_TEXT), + obs_text_type::OBS_TEXT_MULTILINE); + + std::string defaultPath = "C:\\"; + if (ptr) + defaultPath = reinterpret_cast(ptr)->get_shader_file(); + p = obs_properties_add_path(pr, S_CONTENT_FILE, P_TRANSLATE(S_CONTENT_FILE), + obs_path_type::OBS_PATH_FILE, "OBS Studio Effect (*.effect);;Any File (*.*)", defaultPath.c_str()); + + if (ptr) + reinterpret_cast(ptr)->get_properties(pr); + + return pr; +} + +bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *p, obs_data_t *data) { + ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); + switch (shaderType) { + case ShaderType::Text: + obs_property_set_visible(obs_properties_get(pr, S_CONTENT_TEXT), true); + obs_property_set_visible(obs_properties_get(pr, S_CONTENT_FILE), false); + break; + case ShaderType::File: + obs_property_set_visible(obs_properties_get(pr, S_CONTENT_TEXT), false); + obs_property_set_visible(obs_properties_get(pr, S_CONTENT_FILE), true); + break; + } + + return true; +} + +void * Filter::CustomShader::create(obs_data_t *data, obs_source_t *src) { + return new Instance(data, src); +} + +void Filter::CustomShader::destroy(void *ptr) { + delete reinterpret_cast(ptr); +} + +uint32_t Filter::CustomShader::get_width(void *ptr) { + return reinterpret_cast(ptr)->get_width(); +} + +uint32_t Filter::CustomShader::get_height(void *ptr) { + return reinterpret_cast(ptr)->get_height(); +} + +void Filter::CustomShader::update(void *ptr, obs_data_t *data) { + reinterpret_cast(ptr)->update(data); +} + +void Filter::CustomShader::activate(void *ptr) { + reinterpret_cast(ptr)->activate(); +} + +void Filter::CustomShader::deactivate(void *ptr) { + reinterpret_cast(ptr)->activate(); +} + +void Filter::CustomShader::video_tick(void *ptr, float time) { + reinterpret_cast(ptr)->video_tick(time); + +} + +void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) { + reinterpret_cast(ptr)->video_render(effect); +} + +Filter::CustomShader::Instance::Instance(obs_data_t *data, obs_source_t *source) { + m_sourceContext = source; + + update(data); +} + +Filter::CustomShader::Instance::~Instance() { + +} + +void Filter::CustomShader::Instance::update(obs_data_t *data) { + ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); + if (shaderType == ShaderType::Text) { + const char* shaderText = obs_data_get_string(data, S_CONTENT_TEXT); + try { + m_effect = GS::Effect(shaderText, "Text Shader"); + } catch (std::runtime_error& ex) { + const char* filterName = obs_source_get_name(m_sourceContext); + P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); + } + } else if (shaderType == ShaderType::File) { + update_shader_file(obs_data_get_string(data, S_CONTENT_FILE)); + } +} + +uint32_t Filter::CustomShader::Instance::get_width() { + return 0; +} + +uint32_t Filter::CustomShader::Instance::get_height() { + return 0; +} + +void Filter::CustomShader::Instance::activate() { + m_isActive = true; +} + +void Filter::CustomShader::Instance::deactivate() { + m_isActive = false; +} + +void Filter::CustomShader::Instance::video_tick(float time) { + m_shaderFile.lastCheck += time; + if (m_shaderFile.lastCheck >= 0.5) { + update_shader_file(m_shaderFile.filePath); + m_shaderFile.lastCheck = 0; + } +} + +void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { + if (!m_sourceContext || !m_isActive) { + obs_source_skip_video_filter(m_sourceContext); + return; + } + + obs_source_t *parent = obs_filter_get_parent(m_sourceContext); + obs_source_t *target = obs_filter_get_target(m_sourceContext); + if (!parent || !target) { + obs_source_skip_video_filter(m_sourceContext); + return; + } + + uint32_t baseW = obs_source_get_base_width(target), + baseH = obs_source_get_base_height(target); + if (!baseW || !baseH) { + obs_source_skip_video_filter(m_sourceContext); + return; + } + + // Temporary + obs_source_skip_video_filter(m_sourceContext); +} + +void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { + if (m_effect.GetObject() == nullptr) + return; + + m_effectParameters.clear(); + for (auto p : m_effect.GetParameters()) { + std::string p_name = p.GetName(); + std::string p_desc = p_name; + + if (IsSpecialParameter(p_name, p.GetType())) + continue; + + Parameter prm; + prm.origName = p_name; + prm.origType = p.GetType(); + + switch (p.GetType()) { + case GS::EffectParameter::Type::Boolean: + { + prm.names.push_back(p_name); + prm.descs.push_back(p_desc); + m_effectParameters.emplace_back(prm); + obs_properties_add_bool(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str()); + break; + } + case GS::EffectParameter::Type::Float: + case GS::EffectParameter::Type::Float2: + case GS::EffectParameter::Type::Float3: + case GS::EffectParameter::Type::Float4: + { + prm.names.push_back(p_name + "0"); + prm.descs.push_back(p_desc + "[0]"); + if (p.GetType() >= GS::EffectParameter::Type::Float2) { + prm.names.push_back(p_name + "1"); + prm.descs.push_back(p_desc + "[1]"); + } + if (p.GetType() >= GS::EffectParameter::Type::Float3) { + prm.names.push_back(p_name + "2"); + prm.descs.push_back(p_desc + "[2]"); + } + if (p.GetType() >= GS::EffectParameter::Type::Float4) { + prm.names.push_back(p_name + "3"); + prm.descs.push_back(p_desc + "[3]"); + } + + m_effectParameters.emplace_back(prm); + + obs_properties_add_float(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Float2) + obs_properties_add_float(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Float3) + obs_properties_add_float(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Float4) + obs_properties_add_float(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + + break; + } + case GS::EffectParameter::Type::Integer: + case GS::EffectParameter::Type::Integer2: + case GS::EffectParameter::Type::Integer3: + case GS::EffectParameter::Type::Integer4: + { + prm.names.push_back(p_name + "0"); + prm.descs.push_back(p_desc + "[0]"); + if (p.GetType() >= GS::EffectParameter::Type::Integer2) { + prm.names.push_back(p_name + "1"); + prm.descs.push_back(p_desc + "[1]"); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer3) { + prm.names.push_back(p_name + "2"); + prm.descs.push_back(p_desc + "[2]"); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer4) { + prm.names.push_back(p_name + "3"); + prm.descs.push_back(p_desc + "[3]"); + } + + m_effectParameters.emplace_back(prm); + + obs_properties_add_int(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Integer2) + obs_properties_add_int(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Integer3) + obs_properties_add_int(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); + if (p.GetType() >= GS::EffectParameter::Type::Integer4) + obs_properties_add_int(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); + + break; + } + } + + } + return; +} + +void Filter::CustomShader::Instance::update_shader_file(std::string file) { + bool doRefresh = false; + struct stat stats; + + if (file != m_shaderFile.filePath) { + m_shaderFile.filePath = file; + doRefresh = true; + } else if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + doRefresh = doRefresh || + (m_shaderFile.createTime != stats.st_ctime) || + (m_shaderFile.modifiedTime != stats.st_mtime); + m_shaderFile.createTime = stats.st_ctime; + m_shaderFile.modifiedTime = stats.st_mtime; + } + + if (doRefresh) { + try { + m_effect = GS::Effect(m_shaderFile.filePath); + } catch (std::runtime_error& ex) { + const char* filterName = obs_source_get_name(m_sourceContext); + P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); + } + } +} + +std::string Filter::CustomShader::Instance::get_shader_file() { + return m_shaderFile.filePath; +} + +bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { + if (type == GS::EffectParameter::Type::Matrix) { + if (name == "ViewProj") + return true; + + } else if (type == GS::EffectParameter::Type::Float2) { + if (name == "ViewSize") + return true; + + // Suffix Tests + size_t posUnderscore = name.find_last_of('_'); + if (posUnderscore != std::string::npos) { + std::string firstPart, secondPart; + firstPart = name.substr(0, posUnderscore); + secondPart = name.substr(posUnderscore + 1); + + // Texture Specials + if ((secondPart == "size") || (secondPart == "texel")) { + try { + GS::EffectParameter texParam = m_effect.GetParameter(firstPart); + if (texParam.GetType() == GS::EffectParameter::Type::Texture) + return true; + } catch (...) {} + } + } + } else if (type == GS::EffectParameter::Type::Float) { + if (name == "Time" || name == "TimeActive") + return true; + } + + return false; +} diff --git a/source/filter-custom-shader.h b/source/filter-custom-shader.h new file mode 100644 index 00000000..b199a1fc --- /dev/null +++ b/source/filter-custom-shader.h @@ -0,0 +1,111 @@ +/* + * 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 + */ + +#pragma once +#include "plugin.h" +#include "gs-effect.h" +#include +#include + +namespace Filter { + class CustomShader { + public: + CustomShader(); + ~CustomShader(); + + static const char *get_name(void *); + static void get_defaults(obs_data_t *); + static obs_properties_t *get_properties(void *); + static bool modified_properties(obs_properties_t *, + obs_property_t *, obs_data_t *); + + static void *create(obs_data_t *, obs_source_t *); + static void destroy(void *); + static uint32_t get_width(void *); + static uint32_t get_height(void *); + static void update(void *, obs_data_t *); + static void activate(void *); + static void deactivate(void *); + static void video_tick(void *, float); + static void video_render(void *, gs_effect_t *); + + private: + obs_source_info sourceInfo; + + private: + class Instance { + public: + Instance(obs_data_t*, obs_source_t*); + ~Instance(); + + void update(obs_data_t*); + uint32_t get_width(); + uint32_t get_height(); + void activate(); + void deactivate(); + void video_tick(float); + void video_render(gs_effect_t*); + void get_properties(obs_properties_t *); + + protected: + void update_shader_file(std::string file); + std::string get_shader_file(); + + bool IsSpecialParameter(std::string name, GS::EffectParameter::Type type); + void ApplySpecialParameter(); + + private: + obs_source_t *m_sourceContext; + bool m_isActive = true; + + // Shader + struct { + std::string filePath; + time_t createTime, modifiedTime; + size_t size; + float_t lastCheck; + } m_shaderFile; + GS::Effect m_effect; + struct Parameter { + std::vector names; + std::vector descs; + std::string origName; + GS::EffectParameter::Type origType; + + // Texture Input + bool texInputSource = false; + struct { + std::string path; + time_t timeCreated, timeModified; + size_t fileSize; + float_t lastChecked; + } textureFile; + struct { + obs_source_t* source; + std::pair resolution; + } textureSource; + + }; + std::list m_effectParameters; + + + friend class CustomShader; + }; + }; +} From 080048a471dce74838fa21e4b4f682f60dbaf797 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Fri, 19 Jan 2018 20:45:49 +0100 Subject: [PATCH 02/12] filter-custom-shader: UI improvements and initial rendering code --- source/filter-custom-shader.cpp | 550 ++++++++++++++++++++++++-------- source/filter-custom-shader.h | 61 ++-- 2 files changed, 447 insertions(+), 164 deletions(-) diff --git a/source/filter-custom-shader.cpp b/source/filter-custom-shader.cpp index 453e7e94..9beb1508 100644 --- a/source/filter-custom-shader.cpp +++ b/source/filter-custom-shader.cpp @@ -40,19 +40,25 @@ extern "C" { #define DEFAULT_SHADER "" -/* Special Parameters - -Default: -- ViewSize - View Resolution, float2 -- ViewSizeI - View Resolution, integer2 -- Pass - Current Pass Index, integer - -Texture2d: -- $(param)_size - Texture Size, float2 -- $(param)_isize - Texture Size, integer2 -- $(param)_texel - Texture Texel Size, float2 - -*/ +/** Shader Definitions + * - Only one technique, any number of passes. + * - Technique must be named 'Draw'. + * - Parameters are split by the last underscore (_) to determine if it is a special parameter or not. + * + * Predefined Parameters: + * - ViewProj: The current view projection matrix (float4x4). + * - ViewSize: The current rendered size (float2). + * - ViewSizeI: The current rendered size as an int (int2). + * - Pass: The current pass (int). + * - Time: Time passed during total rendering in seconds (float). + * - TimeActive: Time since last activation (float). + * - image: The source being filtered (texture2d). + * + * Texture Special Parameters: + * - $(name)_Size: Texture Size (float2). + * - $(name)_SizeI: Texture Size (int2). + * - $(name)_Texel: Texture Texel Size (1/Texture Size) (float2). +**/ enum class ShaderType : int64_t { Text, @@ -101,7 +107,7 @@ void Filter::CustomShader::get_defaults(obs_data_t *data) { } obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { - obs_properties_t *pr = obs_properties_create(); + obs_properties_t *pr = obs_properties_create_param(ptr, nullptr); obs_property_t *p; p = obs_properties_add_list(pr, S_TYPE, P_TRANSLATE(S_TYPE), obs_combo_type::OBS_COMBO_TYPE_LIST, @@ -113,9 +119,9 @@ obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { p = obs_properties_add_text(pr, S_CONTENT_TEXT, P_TRANSLATE(S_CONTENT_TEXT), obs_text_type::OBS_TEXT_MULTILINE); - std::string defaultPath = "C:\\"; + std::string defaultPath = obs_module_file("effects/displace.effect"); if (ptr) - defaultPath = reinterpret_cast(ptr)->get_shader_file(); + defaultPath = reinterpret_cast(ptr)->GetShaderFile(); p = obs_properties_add_path(pr, S_CONTENT_FILE, P_TRANSLATE(S_CONTENT_FILE), obs_path_type::OBS_PATH_FILE, "OBS Studio Effect (*.effect);;Any File (*.*)", defaultPath.c_str()); @@ -125,7 +131,7 @@ obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { return pr; } -bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *p, obs_data_t *data) { +bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *, obs_data_t *data) { ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); switch (shaderType) { case ShaderType::Text: @@ -138,6 +144,17 @@ bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_propert break; } + Instance* ptr = static_cast(obs_properties_get_param(pr)); + for (Instance::Parameter& prm : ptr->m_effectParameters) { + switch (prm.type) { + case GS::EffectParameter::Type::Texture: + bool isSource = obs_data_get_int(data, prm.uiNames[0].c_str()) == 1; + obs_property_set_visible(obs_properties_get(pr, prm.uiNames[1].c_str()), isSource); + obs_property_set_visible(obs_properties_get(pr, prm.uiNames[2].c_str()), !isSource); + break; + } + } + return true; } @@ -179,14 +196,13 @@ void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) { } Filter::CustomShader::Instance::Instance(obs_data_t *data, obs_source_t *source) { - m_sourceContext = source; - + m_source = source; + m_shaderFile.filePath = obs_module_file("effects/displace.effect"); + m_renderTarget = std::make_unique(GS_RGBA, GS_ZS_NONE); update(data); } -Filter::CustomShader::Instance::~Instance() { - -} +Filter::CustomShader::Instance::~Instance() {} void Filter::CustomShader::Instance::update(obs_data_t *data) { ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); @@ -195,11 +211,147 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { try { m_effect = GS::Effect(shaderText, "Text Shader"); } catch (std::runtime_error& ex) { - const char* filterName = obs_source_get_name(m_sourceContext); + const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); } } else if (shaderType == ShaderType::File) { - update_shader_file(obs_data_get_string(data, S_CONTENT_FILE)); + CheckShaderFile(obs_data_get_string(data, S_CONTENT_FILE), 0.0f); + } + + m_effectParameters.clear(); + if (m_effect.CountParameters() > 0) { + for (auto p : m_effect.GetParameters()) { + std::string p_name = p.GetName(); + std::string p_desc = p_name; + + if (IsSpecialParameter(p_name, p.GetType())) + continue; + + Parameter prm; + prm.name = p_name; + prm.type = p.GetType(); + + switch (p.GetType()) { + case GS::EffectParameter::Type::Boolean: + { + prm.uiNames.push_back(p_name); + prm.uiDescriptions.push_back(p_desc); + prm.value.b = obs_data_get_bool(data, p_name.c_str()); + break; + } + case GS::EffectParameter::Type::Float: + case GS::EffectParameter::Type::Float2: + case GS::EffectParameter::Type::Float3: + case GS::EffectParameter::Type::Float4: + { + { + prm.uiNames.push_back(p_name + "0"); + prm.uiDescriptions.push_back(p_desc + "[0]"); + prm.value.f[0] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float2) { + prm.uiNames.push_back(p_name + "1"); + prm.uiDescriptions.push_back(p_desc + "[1]"); + prm.value.f[1] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float3) { + prm.uiNames.push_back(p_name + "2"); + prm.uiDescriptions.push_back(p_desc + "[2]"); + prm.value.f[2] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float4) { + prm.uiNames.push_back(p_name + "3"); + prm.uiDescriptions.push_back(p_desc + "[3]"); + prm.value.f[3] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + break; + } + case GS::EffectParameter::Type::Integer: + case GS::EffectParameter::Type::Integer2: + case GS::EffectParameter::Type::Integer3: + case GS::EffectParameter::Type::Integer4: + { + { + prm.uiNames.push_back(p_name + "0"); + prm.uiDescriptions.push_back(p_desc + "[0]"); + prm.value.i[0] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer2) { + prm.uiNames.push_back(p_name + "1"); + prm.uiDescriptions.push_back(p_desc + "[1]"); + prm.value.i[1] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer3) { + prm.uiNames.push_back(p_name + "2"); + prm.uiDescriptions.push_back(p_desc + "[2]"); + prm.value.i[2] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer4) { + prm.uiNames.push_back(p_name + "3"); + prm.uiDescriptions.push_back(p_desc + "[3]"); + prm.value.i[3] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + break; + } + case GS::EffectParameter::Type::Texture: + { + prm.uiNames.push_back(p_name + "_type"); + prm.uiDescriptions.push_back(p_desc + " Type"); + prm.value.textureIsSource = obs_data_get_int(data, prm.uiNames.back().c_str()) == 1; + prm.uiNames.push_back(p_name + "_source"); + prm.uiDescriptions.push_back(p_desc); + prm.value.source.name = obs_data_get_string(data, prm.uiNames.back().c_str()); + prm.uiNames.push_back(p_name + "_file"); + prm.uiDescriptions.push_back(p_desc); + if (obs_data_has_user_value(data, prm.uiNames.back().c_str())) { + prm.value.file.path = obs_data_get_string(data, prm.uiNames.back().c_str()); + } else { + prm.value.file.path = obs_module_file("white.png"); + } + break; + } + } + m_effectParameters.emplace_back(prm); + } + } + + for (Parameter& prm : m_effectParameters) { + switch (prm.type) { + case GS::EffectParameter::Type::Boolean: + prm.value.b = obs_data_get_bool(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Float4: + prm.value.f[3] = (float_t)obs_data_get_double(data, prm.uiNames[3].c_str()); + case GS::EffectParameter::Type::Float3: + prm.value.f[2] = (float_t)obs_data_get_double(data, prm.uiNames[2].c_str()); + case GS::EffectParameter::Type::Float2: + prm.value.f[1] = (float_t)obs_data_get_double(data, prm.uiNames[1].c_str()); + case GS::EffectParameter::Type::Float: + prm.value.f[0] = (float_t)obs_data_get_double(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Integer4: + prm.value.i[3] = (int32_t)obs_data_get_int(data, prm.uiNames[3].c_str()); + case GS::EffectParameter::Type::Integer3: + prm.value.i[2] = (int32_t)obs_data_get_int(data, prm.uiNames[2].c_str()); + case GS::EffectParameter::Type::Integer2: + prm.value.i[1] = (int32_t)obs_data_get_int(data, prm.uiNames[1].c_str()); + case GS::EffectParameter::Type::Integer: + prm.value.i[0] = (int32_t)obs_data_get_int(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Texture: + prm.value.textureIsSource = obs_data_get_int(data, prm.uiNames[0].c_str()) == 1; + std::string nName = obs_data_get_string(data, prm.uiNames[1].c_str()); + if (nName != prm.value.source.name) { + prm.value.source.name = nName; + prm.value.source.dirty = true; + } + std::string nPath = obs_data_get_string(data, prm.uiNames[2].c_str()); + if (nPath != prm.value.file.path) { + prm.value.file.path = nPath; + prm.value.file.dirty = true; + } + break; + } } } @@ -220,140 +372,196 @@ void Filter::CustomShader::Instance::deactivate() { } void Filter::CustomShader::Instance::video_tick(float time) { - m_shaderFile.lastCheck += time; - if (m_shaderFile.lastCheck >= 0.5) { - update_shader_file(m_shaderFile.filePath); - m_shaderFile.lastCheck = 0; - } + CheckShaderFile(m_shaderFile.filePath, time); + CheckTextures(time); } void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { - if (!m_sourceContext || !m_isActive) { - obs_source_skip_video_filter(m_sourceContext); + if (!m_source || !m_isActive) { + obs_source_skip_video_filter(m_source); return; } - obs_source_t *parent = obs_filter_get_parent(m_sourceContext); - obs_source_t *target = obs_filter_get_target(m_sourceContext); - if (!parent || !target) { - obs_source_skip_video_filter(m_sourceContext); + obs_source_t *parent = obs_filter_get_parent(m_source); + obs_source_t *target = obs_filter_get_target(m_source); + if (!parent || !target || !m_effect.GetObject()) { + obs_source_skip_video_filter(m_source); return; } uint32_t baseW = obs_source_get_base_width(target), baseH = obs_source_get_base_height(target); if (!baseW || !baseH) { - obs_source_skip_video_filter(m_sourceContext); + obs_source_skip_video_filter(m_source); return; } - // Temporary - obs_source_skip_video_filter(m_sourceContext); + // Render original source to texture. + { + auto op = m_renderTarget->Render(baseW, baseH); + vec4 black; vec4_zero(&black); + gs_ortho(0, (float_t)baseW, 0, (float_t)baseH, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + if (obs_source_process_filter_begin(m_source, GS_RGBA, OBS_NO_DIRECT_RENDERING)) { + obs_source_process_filter_end(m_source, + effect ? effect : obs_get_base_effect(OBS_EFFECT_DEFAULT), baseW, baseH); + } else { + obs_source_skip_video_filter(m_source); + return; + } + } + + // Apply Parameters + try { + for (Parameter& prm : m_effectParameters) { + GS::EffectParameter eprm = m_effect.GetParameter(prm.name); + switch (prm.type) { + case GS::EffectParameter::Type::Boolean: + eprm.SetBoolean(prm.value.b); + break; + case GS::EffectParameter::Type::Integer: + eprm.SetInteger(prm.value.i[0]); + break; + case GS::EffectParameter::Type::Integer2: + eprm.SetInteger2(prm.value.i[0], prm.value.i[1]); + break; + case GS::EffectParameter::Type::Integer3: + eprm.SetInteger3(prm.value.i[0], prm.value.i[1], prm.value.i[2]); + break; + case GS::EffectParameter::Type::Integer4: + eprm.SetInteger4(prm.value.i[0], prm.value.i[1], prm.value.i[2], prm.value.i[3]); + break; + case GS::EffectParameter::Type::Float: + eprm.SetFloat(prm.value.f[0]); + break; + case GS::EffectParameter::Type::Float2: + eprm.SetFloat2(prm.value.f[0], prm.value.f[1]); + break; + case GS::EffectParameter::Type::Float3: + eprm.SetFloat3(prm.value.f[0], prm.value.f[1], prm.value.f[2]); + break; + case GS::EffectParameter::Type::Float4: + eprm.SetFloat4(prm.value.f[0], prm.value.f[1], prm.value.f[2], prm.value.f[3]); + break; + case GS::EffectParameter::Type::Texture: + if (prm.value.textureIsSource) { + if (prm.value.source.rendertarget) { + uint32_t w, h; + w = obs_source_get_width(prm.value.source.source); + h = obs_source_get_height(prm.value.source.source); + { + auto op = prm.value.source.rendertarget->Render(w, h); + vec4 black; vec4_zero(&black); + gs_ortho(0, (float_t)w, 0, (float_t)h, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + obs_source_video_render(prm.value.source.source); + } + eprm.SetTexture(prm.value.source.rendertarget->GetTextureObject()); + } + } else { + if (prm.value.file.texture) { + eprm.SetTexture(prm.value.file.texture); + } + } + break; + } + } + + } catch (...) { + obs_source_skip_video_filter(m_source); + return; + } + + gs_reset_blend_state(); + gs_enable_depth_test(false); + while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { + obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, baseW, baseH, false); + } } -void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { +static bool UpdateSourceListCB(void *ptr, obs_source_t* src) { + obs_property_t* p = (obs_property_t*)ptr; + obs_property_list_add_string(p, obs_source_get_name(src), obs_source_get_name(src)); + return true; +} + +static void UpdateSourceList(obs_property_t* p) { + obs_property_list_clear(p); + obs_enum_sources(UpdateSourceListCB, p); +} + +void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { if (m_effect.GetObject() == nullptr) return; - m_effectParameters.clear(); - for (auto p : m_effect.GetParameters()) { - std::string p_name = p.GetName(); - std::string p_desc = p_name; - - if (IsSpecialParameter(p_name, p.GetType())) - continue; - - Parameter prm; - prm.origName = p_name; - prm.origType = p.GetType(); - - switch (p.GetType()) { + for (Parameter& prm : m_effectParameters) { + switch (prm.type) { case GS::EffectParameter::Type::Boolean: - { - prm.names.push_back(p_name); - prm.descs.push_back(p_desc); - m_effectParameters.emplace_back(prm); - obs_properties_add_bool(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str()); + obs_properties_add_bool(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str()); break; - } case GS::EffectParameter::Type::Float: case GS::EffectParameter::Type::Float2: case GS::EffectParameter::Type::Float3: case GS::EffectParameter::Type::Float4: - { - prm.names.push_back(p_name + "0"); - prm.descs.push_back(p_desc + "[0]"); - if (p.GetType() >= GS::EffectParameter::Type::Float2) { - prm.names.push_back(p_name + "1"); - prm.descs.push_back(p_desc + "[1]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Float3) { - prm.names.push_back(p_name + "2"); - prm.descs.push_back(p_desc + "[2]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Float4) { - prm.names.push_back(p_name + "3"); - prm.descs.push_back(p_desc + "[3]"); - } - - m_effectParameters.emplace_back(prm); - - obs_properties_add_float(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float2) - obs_properties_add_float(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float3) - obs_properties_add_float(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float4) - obs_properties_add_float(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - + obs_properties_add_float(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float2) + obs_properties_add_float(pr, prm.uiNames[1].c_str(), prm.uiDescriptions[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float3) + obs_properties_add_float(pr, prm.uiNames[2].c_str(), prm.uiDescriptions[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float4) + obs_properties_add_float(pr, prm.uiNames[3].c_str(), prm.uiDescriptions[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); break; - } case GS::EffectParameter::Type::Integer: case GS::EffectParameter::Type::Integer2: case GS::EffectParameter::Type::Integer3: case GS::EffectParameter::Type::Integer4: - { - prm.names.push_back(p_name + "0"); - prm.descs.push_back(p_desc + "[0]"); - if (p.GetType() >= GS::EffectParameter::Type::Integer2) { - prm.names.push_back(p_name + "1"); - prm.descs.push_back(p_desc + "[1]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Integer3) { - prm.names.push_back(p_name + "2"); - prm.descs.push_back(p_desc + "[2]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Integer4) { - prm.names.push_back(p_name + "3"); - prm.descs.push_back(p_desc + "[3]"); - } - - m_effectParameters.emplace_back(prm); - - obs_properties_add_int(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer2) - obs_properties_add_int(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer3) - obs_properties_add_int(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer4) - obs_properties_add_int(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - + obs_properties_add_int(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer2) + obs_properties_add_int(pr, prm.uiNames[1].c_str(), prm.uiDescriptions[1].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer3) + obs_properties_add_int(pr, prm.uiNames[2].c_str(), prm.uiDescriptions[2].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer4) + obs_properties_add_int(pr, prm.uiNames[3].c_str(), prm.uiDescriptions[3].c_str(), INT_MIN, INT_MAX, 1); break; - } - } + case GS::EffectParameter::Type::Texture: + obs_property * pt = obs_properties_add_list(pr, + prm.uiNames[0].c_str(), + prm.uiDescriptions[0].c_str(), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(pt, "File", 0); + obs_property_list_add_int(pt, "Source", 1); + obs_property_set_modified_callback(pt, modified_properties); + pt = obs_properties_add_list(pr, + prm.uiNames[1].c_str(), + prm.uiDescriptions[1].c_str(), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + UpdateSourceList(pt); + + obs_properties_add_path(pr, + prm.uiNames[2].c_str(), + prm.uiDescriptions[2].c_str(), + OBS_PATH_FILE, "", prm.value.file.path.c_str()); + break; + } } return; } -void Filter::CustomShader::Instance::update_shader_file(std::string file) { +void Filter::CustomShader::Instance::CheckShaderFile(std::string file, float_t time) { bool doRefresh = false; - struct stat stats; - if (file != m_shaderFile.filePath) { m_shaderFile.filePath = file; doRefresh = true; - } else if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + } + + m_shaderFile.lastCheck += time; + if (m_shaderFile.lastCheck < 0.5f && doRefresh == false) + return; + m_shaderFile.lastCheck = m_shaderFile.lastCheck - 0.5f; + + struct stat stats; + if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { doRefresh = doRefresh || (m_shaderFile.createTime != stats.st_ctime) || (m_shaderFile.modifiedTime != stats.st_mtime); @@ -365,45 +573,105 @@ void Filter::CustomShader::Instance::update_shader_file(std::string file) { try { m_effect = GS::Effect(m_shaderFile.filePath); } catch (std::runtime_error& ex) { - const char* filterName = obs_source_get_name(m_sourceContext); + const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); } } } -std::string Filter::CustomShader::Instance::get_shader_file() { +std::string Filter::CustomShader::Instance::GetShaderFile() { return m_shaderFile.filePath; } -bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { - if (type == GS::EffectParameter::Type::Matrix) { - if (name == "ViewProj") - return true; +void Filter::CustomShader::Instance::CheckTextures(float_t time) { + for (Parameter& prm : m_effectParameters) { + if (prm.type != GS::EffectParameter::Type::Texture) + continue; - } else if (type == GS::EffectParameter::Type::Float2) { - if (name == "ViewSize") - return true; - - // Suffix Tests - size_t posUnderscore = name.find_last_of('_'); - if (posUnderscore != std::string::npos) { - std::string firstPart, secondPart; - firstPart = name.substr(0, posUnderscore); - secondPart = name.substr(posUnderscore + 1); + if (prm.value.textureIsSource) { + if (!prm.value.source.rendertarget) { + prm.value.source.rendertarget = std::make_shared(GS_RGBA, GS_ZS_NONE); + } + if (prm.value.source.dirty || !prm.value.source.source) { + if (prm.value.source.source) + obs_source_release(prm.value.source.source); + prm.value.source.source = obs_get_source_by_name(prm.value.source.name.c_str()); + } + } else { + bool doRefresh = false; + if (prm.value.file.dirty || !prm.value.file.texture) { + doRefresh = true; + } - // Texture Specials - if ((secondPart == "size") || (secondPart == "texel")) { + prm.value.file.lastCheck += time; + if (prm.value.file.lastCheck < 0.5f && doRefresh == false) + continue; + prm.value.file.lastCheck = prm.value.file.lastCheck - 0.5f; + + struct stat stats; + if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + doRefresh = doRefresh || + (prm.value.file.createTime != stats.st_ctime) || + (prm.value.file.modifiedTime != stats.st_mtime) || + (prm.value.file.fileSize != stats.st_size); + prm.value.file.createTime = stats.st_ctime; + prm.value.file.modifiedTime = stats.st_mtime; + prm.value.file.fileSize = stats.st_size; + } + + if (doRefresh) { try { - GS::EffectParameter texParam = m_effect.GetParameter(firstPart); - if (texParam.GetType() == GS::EffectParameter::Type::Texture) - return true; - } catch (...) {} + prm.value.file.texture = std::make_shared(prm.value.file.path); + } catch (std::runtime_error& ex) { + const char* filterName = obs_source_get_name(m_source); + P_LOG_ERROR("[%s] Loading texture file '%s' failed with error(s): %s", + filterName, prm.value.file.path.c_str(), ex.what()); + } } } - } else if (type == GS::EffectParameter::Type::Float) { - if (name == "Time" || name == "TimeActive") + } +} + +bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { + std::pair reservedParameters[] = { + { "ViewProj", GS::EffectParameter::Type::Matrix }, + { "ViewSize", GS::EffectParameter::Type::Matrix }, + { "ViewSizeI", GS::EffectParameter::Type::Integer2 }, + { "Pass", GS::EffectParameter::Type::Integer }, + { "Time", GS::EffectParameter::Type::Float }, + { "TimeActive", GS::EffectParameter::Type::Float }, + { "image", GS::EffectParameter::Type::Texture } + }; + std::pair textureParameters[] = { + { "Size", GS::EffectParameter::Type::Float2 }, + { "SizeI", GS::EffectParameter::Type::Integer2 }, + { "Texel", GS::EffectParameter::Type::Float2 } + }; + + for (auto& kv : reservedParameters) { + if ((name == kv.first) && (type == kv.second)) return true; } + // Split by last underscore(_) (if there is one). + size_t posUnderscore = name.find_last_of('_'); + if (posUnderscore != std::string::npos) { + std::string firstPart, secondPart; + firstPart = name.substr(0, posUnderscore); + secondPart = name.substr(posUnderscore + 1); + + try { + GS::EffectParameter prm = m_effect.GetParameter(firstPart); + if (prm.GetType() == GS::EffectParameter::Type::Texture) { + for (auto& kv : reservedParameters) { + if ((secondPart == kv.first) && (type == kv.second)) + return true; + } + } + } catch (...) { + return false; + } + } + return false; } diff --git a/source/filter-custom-shader.h b/source/filter-custom-shader.h index b199a1fc..85e751e6 100644 --- a/source/filter-custom-shader.h +++ b/source/filter-custom-shader.h @@ -20,8 +20,10 @@ #pragma once #include "plugin.h" #include "gs-effect.h" +#include "gs-rendertarget.h" #include #include +#include namespace Filter { class CustomShader { @@ -53,7 +55,7 @@ namespace Filter { public: Instance(obs_data_t*, obs_source_t*); ~Instance(); - + void update(obs_data_t*); uint32_t get_width(); uint32_t get_height(); @@ -63,15 +65,15 @@ namespace Filter { void video_render(gs_effect_t*); void get_properties(obs_properties_t *); - protected: - void update_shader_file(std::string file); - std::string get_shader_file(); + private: + void CheckShaderFile(std::string file, float_t time); + std::string GetShaderFile(); + void CheckTextures(float_t time); bool IsSpecialParameter(std::string name, GS::EffectParameter::Type type); - void ApplySpecialParameter(); - + private: - obs_source_t *m_sourceContext; + obs_source_t * m_source; bool m_isActive = true; // Shader @@ -81,29 +83,42 @@ namespace Filter { size_t size; float_t lastCheck; } m_shaderFile; + GS::Effect m_effect; struct Parameter { - std::vector names; - std::vector descs; - std::string origName; - GS::EffectParameter::Type origType; + std::string name; + GS::EffectParameter::Type type; - // Texture Input - bool texInputSource = false; - struct { - std::string path; - time_t timeCreated, timeModified; - size_t fileSize; - float_t lastChecked; - } textureFile; - struct { - obs_source_t* source; - std::pair resolution; - } textureSource; + std::vector uiNames; + std::vector uiDescriptions; + struct { + union { + int32_t i[4]; + float_t f[4]; + bool b; + }; + bool textureIsSource = false; + struct { + bool dirty = false; + std::string name; + obs_source_t* source = nullptr; + std::shared_ptr rendertarget; + } source; + struct { + bool dirty = false; + std::string path; + time_t createTime, modifiedTime; + size_t fileSize; + float_t lastCheck; + std::shared_ptr texture; + } file; + } value; }; std::list m_effectParameters; + std::unique_ptr m_renderTarget; + friend class CustomShader; }; From 107103001b5d6dea008891b01b413a145d376f7e Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 25 Jan 2018 09:12:47 +0100 Subject: [PATCH 03/12] gs-effect: Add HasParameter and fix Set*Array HasParameter can be used to safely check if a parameter exists and such should be preferred over try-catch blocks. Additionally, SetFloat2, SetFloat3, SetFloat4, SetFloatArray, SetInteger2, SetInteger3, SetInteger4 and SetIntegerArray should no longer cause rendering issues due to invalid buffer sizes. --- source/gs-effect.cpp | 37 +++++++++++++++++++++++++------------ source/gs-effect.h | 2 ++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/source/gs-effect.cpp b/source/gs-effect.cpp index 9a743410..8eaa91b3 100644 --- a/source/gs-effect.cpp +++ b/source/gs-effect.cpp @@ -30,7 +30,7 @@ GS::Effect::Effect() { m_effect = nullptr; } -GS::Effect::Effect(std::string file) { +GS::Effect::Effect(std::string file) { obs_enter_graphics(); char* errorMessage = nullptr; m_effect = gs_effect_create_from_file(file.c_str(), &errorMessage); @@ -92,6 +92,19 @@ GS::EffectParameter GS::Effect::GetParameter(size_t idx) { return EffectParameter(param); } +bool GS::Effect::HasParameter(std::string name) { + gs_eparam_t* param = gs_effect_get_param_by_name(m_effect, name.c_str()); + return (param != nullptr); +} + +bool GS::Effect::HasParameter(std::string name, EffectParameter::Type type) { + gs_eparam_t* param = gs_effect_get_param_by_name(m_effect, name.c_str()); + if (param == nullptr) + return false; + GS::EffectParameter eprm(param); + return eprm.GetType() == type; +} + GS::EffectParameter GS::Effect::GetParameter(std::string name) { gs_eparam_t* param = gs_effect_get_param_by_name(m_effect, name.c_str()); if (!param) @@ -170,8 +183,8 @@ void GS::EffectParameter::SetFloat2(vec2& v) { void GS::EffectParameter::SetFloat2(float_t x, float_t y) { if (GetType() != Type::Float2) throw std::bad_cast(); - float_t v[] = { x, y }; - SetFloatArray(v, 2); + vec2 v = { x, y }; + gs_effect_set_vec2(m_param, &v); } void GS::EffectParameter::SetFloat3(vec3& v) { @@ -183,8 +196,8 @@ void GS::EffectParameter::SetFloat3(vec3& v) { void GS::EffectParameter::SetFloat3(float_t x, float_t y, float_t z) { if (GetType() != Type::Float3) throw std::bad_cast(); - float_t v[] = { x, y, z }; - SetFloatArray(v, 3); + vec3 v = { x, y, z }; + gs_effect_set_vec3(m_param, &v); } void GS::EffectParameter::SetFloat4(vec4& v) { @@ -196,14 +209,14 @@ void GS::EffectParameter::SetFloat4(vec4& v) { void GS::EffectParameter::SetFloat4(float_t x, float_t y, float_t z, float_t w) { if (GetType() != Type::Float4) throw std::bad_cast(); - float_t v[] = { x, y, z, w }; - SetFloatArray(v, 4); + vec4 v = { x, y, z, w }; + gs_effect_set_vec4(m_param, &v); } void GS::EffectParameter::SetFloatArray(float_t v[], size_t sz) { if ((GetType() != Type::Float) && (GetType() != Type::Float2) && (GetType() != Type::Float3) && (GetType() != Type::Float4)) throw std::bad_cast(); - gs_effect_set_val(m_param, v, sz); + gs_effect_set_val(m_param, v, sizeof(float_t) * sz); } void GS::EffectParameter::SetInteger(int32_t x) { @@ -216,27 +229,27 @@ void GS::EffectParameter::SetInteger2(int32_t x, int32_t y) { if (GetType() != Type::Integer2) throw std::bad_cast(); int32_t v[] = { x, y }; - gs_effect_set_val(m_param, v, 2); + gs_effect_set_val(m_param, v, sizeof(int) * 2); } void GS::EffectParameter::SetInteger3(int32_t x, int32_t y, int32_t z) { if (GetType() != Type::Integer3) throw std::bad_cast(); int32_t v[] = { x, y, z }; - gs_effect_set_val(m_param, v, 3); + gs_effect_set_val(m_param, v, sizeof(int) * 3); } void GS::EffectParameter::SetInteger4(int32_t x, int32_t y, int32_t z, int32_t w) { if (GetType() != Type::Integer4) throw std::bad_cast(); int32_t v[] = { x, y, z, w }; - gs_effect_set_val(m_param, v, 4); + gs_effect_set_val(m_param, v, sizeof(int) * 4); } void GS::EffectParameter::SetIntegerArray(int32_t v[], size_t sz) { if ((GetType() != Type::Integer) && (GetType() != Type::Integer2) && (GetType() != Type::Integer3) && (GetType() != Type::Integer4)) throw std::bad_cast(); - gs_effect_set_val(m_param, v, sz); + gs_effect_set_val(m_param, v, sizeof(int) * sz); } void GS::EffectParameter::SetMatrix(matrix4& v) { diff --git a/source/gs-effect.h b/source/gs-effect.h index 0b51550e..ec62ad48 100644 --- a/source/gs-effect.h +++ b/source/gs-effect.h @@ -99,6 +99,8 @@ namespace GS { std::list GetParameters(); EffectParameter GetParameter(size_t idx); EffectParameter GetParameter(std::string name); + bool HasParameter(std::string name); + bool HasParameter(std::string name, EffectParameter::Type type); protected: gs_effect_t* m_effect; From eccd95c68cbae8e3f82cb154a3f988ae13701e06 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 25 Jan 2018 10:26:09 +0100 Subject: [PATCH 04/12] filter-custom-shader: First working version with several fixes - Shaders now have all promised special variables available to them. - 'Pass' (int) is no longer a special variable. - Files should now properly update every half second. - Fixed a ton of compiler warnings. - Forced loading shaders from text instead of files to avoid caching. - Fixed file modification tests always failing. - Actually renders shaders now. Known issues: - Parameters aren't updated with file modifications unless the file is actually changed in the UI. --- source/filter-custom-shader.cpp | 177 ++++++++++++++++++++++++-------- source/filter-custom-shader.h | 18 ++-- 2 files changed, 142 insertions(+), 53 deletions(-) diff --git a/source/filter-custom-shader.cpp b/source/filter-custom-shader.cpp index 9beb1508..f06db8a9 100644 --- a/source/filter-custom-shader.cpp +++ b/source/filter-custom-shader.cpp @@ -21,6 +21,7 @@ #include "strings.h" #include #include +#include extern "C" { #pragma warning (push) @@ -49,7 +50,6 @@ extern "C" { * - ViewProj: The current view projection matrix (float4x4). * - ViewSize: The current rendered size (float2). * - ViewSizeI: The current rendered size as an int (int2). - * - Pass: The current pass (int). * - Time: Time passed during total rendering in seconds (float). * - TimeActive: Time since last activation (float). * - image: The source being filtered (texture2d). @@ -197,8 +197,19 @@ void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) { Filter::CustomShader::Instance::Instance(obs_data_t *data, obs_source_t *source) { m_source = source; - m_shaderFile.filePath = obs_module_file("effects/displace.effect"); + + m_effect.path = obs_module_file("effects/displace.effect"); + m_effect.createTime = time_t(0); + m_effect.modifiedTime = time_t(0); + m_effect.size = 0; + m_effect.lastCheck = 0; + m_effect.effect = nullptr; + m_renderTarget = std::make_unique(GS_RGBA, GS_ZS_NONE); + + m_activeTime = 0.0f; + m_renderTime = 0.0f; + update(data); } @@ -209,7 +220,7 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { if (shaderType == ShaderType::Text) { const char* shaderText = obs_data_get_string(data, S_CONTENT_TEXT); try { - m_effect = GS::Effect(shaderText, "Text Shader"); + m_effect.effect = std::make_unique(shaderText, "Text Shader"); } catch (std::runtime_error& ex) { const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); @@ -219,8 +230,8 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { } m_effectParameters.clear(); - if (m_effect.CountParameters() > 0) { - for (auto p : m_effect.GetParameters()) { + if (m_effect.effect && m_effect.effect->CountParameters() > 0) { + for (auto p : m_effect.effect->GetParameters()) { std::string p_name = p.GetName(); std::string p_desc = p_name; @@ -247,22 +258,22 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { { prm.uiNames.push_back(p_name + "0"); prm.uiDescriptions.push_back(p_desc + "[0]"); - prm.value.f[0] = obs_data_get_double(data, prm.uiNames.back().c_str()); + prm.value.f[0] = (float_t)obs_data_get_double(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Float2) { prm.uiNames.push_back(p_name + "1"); prm.uiDescriptions.push_back(p_desc + "[1]"); - prm.value.f[1] = obs_data_get_double(data, prm.uiNames.back().c_str()); + prm.value.f[1] = (float_t)obs_data_get_double(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Float3) { prm.uiNames.push_back(p_name + "2"); prm.uiDescriptions.push_back(p_desc + "[2]"); - prm.value.f[2] = obs_data_get_double(data, prm.uiNames.back().c_str()); + prm.value.f[2] = (float_t)obs_data_get_double(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Float4) { prm.uiNames.push_back(p_name + "3"); prm.uiDescriptions.push_back(p_desc + "[3]"); - prm.value.f[3] = obs_data_get_double(data, prm.uiNames.back().c_str()); + prm.value.f[3] = (float_t)obs_data_get_double(data, prm.uiNames.back().c_str()); } break; } @@ -274,22 +285,22 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { { prm.uiNames.push_back(p_name + "0"); prm.uiDescriptions.push_back(p_desc + "[0]"); - prm.value.i[0] = obs_data_get_int(data, prm.uiNames.back().c_str()); + prm.value.i[0] = (int32_t)obs_data_get_int(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Integer2) { prm.uiNames.push_back(p_name + "1"); prm.uiDescriptions.push_back(p_desc + "[1]"); - prm.value.i[1] = obs_data_get_int(data, prm.uiNames.back().c_str()); + prm.value.i[1] = (int32_t)obs_data_get_int(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Integer3) { prm.uiNames.push_back(p_name + "2"); prm.uiDescriptions.push_back(p_desc + "[2]"); - prm.value.i[2] = obs_data_get_int(data, prm.uiNames.back().c_str()); + prm.value.i[2] = (int32_t)obs_data_get_int(data, prm.uiNames.back().c_str()); } if (p.GetType() >= GS::EffectParameter::Type::Integer4) { prm.uiNames.push_back(p_name + "3"); prm.uiDescriptions.push_back(p_desc + "[3]"); - prm.value.i[3] = obs_data_get_int(data, prm.uiNames.back().c_str()); + prm.value.i[3] = (int32_t)obs_data_get_int(data, prm.uiNames.back().c_str()); } break; } @@ -365,6 +376,7 @@ uint32_t Filter::CustomShader::Instance::get_height() { void Filter::CustomShader::Instance::activate() { m_isActive = true; + m_activeTime = 0.0f; } void Filter::CustomShader::Instance::deactivate() { @@ -372,8 +384,11 @@ void Filter::CustomShader::Instance::deactivate() { } void Filter::CustomShader::Instance::video_tick(float time) { - CheckShaderFile(m_shaderFile.filePath, time); + CheckShaderFile(m_effect.path, time); CheckTextures(time); + if (m_isActive) + m_activeTime += time; + m_renderTime += time; } void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { @@ -384,7 +399,7 @@ void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { obs_source_t *parent = obs_filter_get_parent(m_source); obs_source_t *target = obs_filter_get_target(m_source); - if (!parent || !target || !m_effect.GetObject()) { + if (!parent || !target || !m_effect.effect) { obs_source_skip_video_filter(m_source); return; } @@ -405,16 +420,35 @@ void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { if (obs_source_process_filter_begin(m_source, GS_RGBA, OBS_NO_DIRECT_RENDERING)) { obs_source_process_filter_end(m_source, effect ? effect : obs_get_base_effect(OBS_EFFECT_DEFAULT), baseW, baseH); - } else { - obs_source_skip_video_filter(m_source); - return; } } + gs_texture_t* sourceTexture = m_renderTarget->GetTextureObject(); + if (!sourceTexture) { + obs_source_skip_video_filter(m_source); + return; + } // Apply Parameters try { + if (m_effect.effect->HasParameter("ViewSize", GS::EffectParameter::Type::Float2)) + m_effect.effect->GetParameter("ViewSize").SetFloat2(float_t(baseW), float_t(baseH)); + if (m_effect.effect->HasParameter("ViewSizeI", GS::EffectParameter::Type::Integer2)) + m_effect.effect->GetParameter("ViewSizeI").SetInteger2(baseW, baseH); + if (m_effect.effect->HasParameter("Time", GS::EffectParameter::Type::Float)) + m_effect.effect->GetParameter("Time").SetFloat(m_renderTime); + if (m_effect.effect->HasParameter("TimeActive", GS::EffectParameter::Type::Float)) + m_effect.effect->GetParameter("TimeActive").SetFloat(m_activeTime); + + /// "image" Specials + if (m_effect.effect->HasParameter("image_Size", GS::EffectParameter::Type::Float2)) + m_effect.effect->GetParameter("image_Size").SetFloat2(float_t(baseW), float_t(baseH)); + if (m_effect.effect->HasParameter("image_SizeI", GS::EffectParameter::Type::Float2)) + m_effect.effect->GetParameter("image_SizeI").SetInteger2(baseW, baseH); + if (m_effect.effect->HasParameter("image_Texel", GS::EffectParameter::Type::Float2)) + m_effect.effect->GetParameter("image_Texel").SetFloat2(1.0f / float_t(baseW), 1.0f / float_t(baseH)); + for (Parameter& prm : m_effectParameters) { - GS::EffectParameter eprm = m_effect.GetParameter(prm.name); + GS::EffectParameter eprm = m_effect.effect->GetParameter(prm.name); switch (prm.type) { case GS::EffectParameter::Type::Boolean: eprm.SetBoolean(prm.value.b); @@ -445,7 +479,7 @@ void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { break; case GS::EffectParameter::Type::Texture: if (prm.value.textureIsSource) { - if (prm.value.source.rendertarget) { + if (prm.value.source.rendertarget && prm.value.source.source) { uint32_t w, h; w = obs_source_get_width(prm.value.source.source); h = obs_source_get_height(prm.value.source.source); @@ -474,8 +508,8 @@ void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { gs_reset_blend_state(); gs_enable_depth_test(false); - while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { - obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, baseW, baseH, false); + while (gs_effect_loop(m_effect.effect->GetObject(), "Draw")) { + obs_source_draw(sourceTexture, 0, 0, baseW, baseH, false); } } @@ -491,7 +525,7 @@ static void UpdateSourceList(obs_property_t* p) { } void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { - if (m_effect.GetObject() == nullptr) + if (!m_effect.effect) return; for (Parameter& prm : m_effectParameters) { @@ -550,28 +584,58 @@ void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { void Filter::CustomShader::Instance::CheckShaderFile(std::string file, float_t time) { bool doRefresh = false; - if (file != m_shaderFile.filePath) { - m_shaderFile.filePath = file; + + // Update if the paths are different. + if (file != m_effect.path) { + m_effect.path = file; doRefresh = true; } - m_shaderFile.lastCheck += time; - if (m_shaderFile.lastCheck < 0.5f && doRefresh == false) + if (file.empty()) { + m_effect.effect = nullptr; return; - m_shaderFile.lastCheck = m_shaderFile.lastCheck - 0.5f; + } + + // Don't check for updates if the last update was less than 1/2 seconds away. + m_effect.lastCheck += time; + if (m_effect.lastCheck < 0.5f) { + if (!doRefresh) + return; + } else { + m_effect.lastCheck = m_effect.lastCheck - 0.5f; + } struct stat stats; - if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + if (os_stat(m_effect.path.c_str(), &stats) == 0) { doRefresh = doRefresh || - (m_shaderFile.createTime != stats.st_ctime) || - (m_shaderFile.modifiedTime != stats.st_mtime); - m_shaderFile.createTime = stats.st_ctime; - m_shaderFile.modifiedTime = stats.st_mtime; + (m_effect.createTime != stats.st_ctime) || + (m_effect.modifiedTime != stats.st_mtime); + m_effect.createTime = stats.st_ctime; + m_effect.modifiedTime = stats.st_mtime; } + if (!m_effect.effect) + doRefresh = true; + if (doRefresh) { try { - m_effect = GS::Effect(m_shaderFile.filePath); + std::vector shaderContent; + + { // gs_effect_create_from_file caches results, which is bad for us. + std::ifstream fs(file.c_str(), std::ios::binary); + if (fs.bad()) + throw std::runtime_error("Failed to open file."); + size_t beg = fs.tellg(); + fs.seekg(0, std::ios::end); + size_t sz = size_t(fs.tellg()) - beg; + shaderContent.resize(sz + 1); + fs.seekg(0, std::ios::beg); + fs.read(shaderContent.data(), sz); + fs.close(); + shaderContent[sz] = '\0'; + } + + m_effect.effect = std::make_unique(shaderContent.data(), m_effect.path); } catch (std::runtime_error& ex) { const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); @@ -580,36 +644,61 @@ void Filter::CustomShader::Instance::CheckShaderFile(std::string file, float_t t } std::string Filter::CustomShader::Instance::GetShaderFile() { - return m_shaderFile.filePath; + return m_effect.path; } void Filter::CustomShader::Instance::CheckTextures(float_t time) { + for (Parameter& prm : m_effectParameters) { if (prm.type != GS::EffectParameter::Type::Texture) continue; if (prm.value.textureIsSource) { - if (!prm.value.source.rendertarget) { - prm.value.source.rendertarget = std::make_shared(GS_RGBA, GS_ZS_NONE); + // If the source field is empty, simply clear the source reference. + if (prm.value.source.name.empty()) { + if (prm.value.source.source) + obs_source_release(m_source); + prm.value.source.source = nullptr; + continue; } + + // Ensure that a render target exists. + if (!prm.value.source.rendertarget) + prm.value.source.rendertarget = std::make_shared(GS_RGBA, GS_ZS_NONE); + + // Finally check if the source property was modified or is empty. if (prm.value.source.dirty || !prm.value.source.source) { + prm.value.source.dirty = false; if (prm.value.source.source) obs_source_release(prm.value.source.source); prm.value.source.source = obs_get_source_by_name(prm.value.source.name.c_str()); } } else { bool doRefresh = false; + + // If the path is empty, don't attempt to update any files and simply null the texture. + if (prm.value.file.path.empty()) { + prm.value.file.texture = nullptr; + continue; + } + + // If the property was modified or the texture is empty, force a refresh. if (prm.value.file.dirty || !prm.value.file.texture) { doRefresh = true; } + // Skip testing if the last test was less than 1/2 of a second away. prm.value.file.lastCheck += time; - if (prm.value.file.lastCheck < 0.5f && doRefresh == false) - continue; - prm.value.file.lastCheck = prm.value.file.lastCheck - 0.5f; + if (prm.value.file.lastCheck < 0.5f) { + if (!doRefresh) + continue; + } else { + prm.value.file.lastCheck = prm.value.file.lastCheck - 0.5f; + } + // Test if the file was modified. struct stat stats; - if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + if (os_stat(prm.value.file.path.c_str(), &stats) == 0) { doRefresh = doRefresh || (prm.value.file.createTime != stats.st_ctime) || (prm.value.file.modifiedTime != stats.st_mtime) || @@ -620,6 +709,7 @@ void Filter::CustomShader::Instance::CheckTextures(float_t time) { } if (doRefresh) { + prm.value.file.dirty = false; try { prm.value.file.texture = std::make_shared(prm.value.file.path); } catch (std::runtime_error& ex) { @@ -635,9 +725,8 @@ void Filter::CustomShader::Instance::CheckTextures(float_t time) { bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { std::pair reservedParameters[] = { { "ViewProj", GS::EffectParameter::Type::Matrix }, - { "ViewSize", GS::EffectParameter::Type::Matrix }, + { "ViewSize", GS::EffectParameter::Type::Float2 }, { "ViewSizeI", GS::EffectParameter::Type::Integer2 }, - { "Pass", GS::EffectParameter::Type::Integer }, { "Time", GS::EffectParameter::Type::Float }, { "TimeActive", GS::EffectParameter::Type::Float }, { "image", GS::EffectParameter::Type::Texture } @@ -661,7 +750,7 @@ bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::Ef secondPart = name.substr(posUnderscore + 1); try { - GS::EffectParameter prm = m_effect.GetParameter(firstPart); + GS::EffectParameter prm = m_effect.effect->GetParameter(firstPart); if (prm.GetType() == GS::EffectParameter::Type::Texture) { for (auto& kv : reservedParameters) { if ((secondPart == kv.first) && (type == kv.second)) diff --git a/source/filter-custom-shader.h b/source/filter-custom-shader.h index 85e751e6..2d8e9766 100644 --- a/source/filter-custom-shader.h +++ b/source/filter-custom-shader.h @@ -75,16 +75,19 @@ namespace Filter { private: obs_source_t * m_source; bool m_isActive = true; + std::unique_ptr m_renderTarget; + + float_t m_activeTime, m_renderTime; // Shader - struct { - std::string filePath; + struct Effect { + std::string path; time_t createTime, modifiedTime; size_t size; float_t lastCheck; - } m_shaderFile; + std::unique_ptr effect; + } m_effect; - GS::Effect m_effect; struct Parameter { std::string name; GS::EffectParameter::Type type; @@ -99,13 +102,13 @@ namespace Filter { bool b; }; bool textureIsSource = false; - struct { + struct Source { bool dirty = false; std::string name; obs_source_t* source = nullptr; std::shared_ptr rendertarget; } source; - struct { + struct File { bool dirty = false; std::string path; time_t createTime, modifiedTime; @@ -117,9 +120,6 @@ namespace Filter { }; std::list m_effectParameters; - std::unique_ptr m_renderTarget; - - friend class CustomShader; }; }; From 01bf510a288ebbda179fb5c0ef7d14a03ee05787 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sat, 24 Feb 2018 04:35:49 +0100 Subject: [PATCH 05/12] util-memory: Use standard alloc/free and fix incorrect aligned length The custom allocator occasionally returned memory that was aligned, but did not have enough space to store the actual size due to a calculation error in the size. This resulted in situations where allocating 1022 bytes would give you a writable buffer of only 1020 bytes or less, or also known as writing into unknown memory, possibly even the heap. This is now fixed by doubling the padding used. Additionally it will now default to using standard allocators, which should work better and rely on the Compiler. --- source/util-memory.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/source/util-memory.cpp b/source/util-memory.cpp index cbe70241..c97efc9b 100644 --- a/source/util-memory.cpp +++ b/source/util-memory.cpp @@ -19,25 +19,50 @@ #include "util-memory.h" #include +#define USE_STD_ALLOC_FREE void* util::malloc_aligned(size_t align, size_t size) { +#ifdef USE_STD_ALLOC_FREE +#if defined(_MSC_VER) +#ifdef DEBUG + return _aligned_malloc_dbg(size, align); +#else + return _aligned_malloc(size, align); +#endif +#else + return aligned_malloc(align, size); +#endif +#else // Ensure that we have space for the pointer and the data. - size_t asize = aligned_offset(align, size + sizeof(void*)); + size_t asize = aligned_offset(align, size + (sizeof(void*) * 2)); // Allocate memory and store integer representation of pointer. void* ptr = malloc(asize); // Calculate actual aligned position - intptr_t ptr_off = aligned_offset(align, reinterpret_cast(ptr)+sizeof(void*)); + intptr_t ptr_off = aligned_offset(align, reinterpret_cast(ptr) + sizeof(void*)); // Store actual pointer at ptr_off - sizeof(void*). *reinterpret_cast(ptr_off - sizeof(void*)) = reinterpret_cast(ptr); // Return aligned pointer return reinterpret_cast(ptr_off); +#endif } void util::free_aligned(void* mem) { - void* ptr = reinterpret_cast(*reinterpret_cast(static_cast(mem)-sizeof(void*))); +#ifdef USE_STD_ALLOC_FREE +#if defined(_MSC_VER) +#ifdef DEBUG + _aligned_free_dbg(mem); +#else + _aligned_free(mem); +#endif +#else + free(mem); +#endif +#else + void* ptr = reinterpret_cast(*reinterpret_cast(static_cast(mem) - sizeof(void*))); free(ptr); +#endif } From 7d065c131d6b4459b58eaebb9b8b8aeb8360979d Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 15:10:19 +0100 Subject: [PATCH 06/12] gs-texture: Allow creation from existing gs_texture_t* --- source/gs-texture.cpp | 2 +- source/gs-texture.h | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/source/gs-texture.cpp b/source/gs-texture.cpp index 43923166..baa25806 100644 --- a/source/gs-texture.cpp +++ b/source/gs-texture.cpp @@ -178,7 +178,7 @@ GS::Texture::Texture(Texture& other) { } GS::Texture::~Texture() { - if (m_texture) { + if (m_isOwner && m_texture) { obs_enter_graphics(); switch (gs_get_texture_type(m_texture)) { case GS_TEXTURE_2D: diff --git a/source/gs-texture.h b/source/gs-texture.h index d8d42082..319fc639 100644 --- a/source/gs-texture.h +++ b/source/gs-texture.h @@ -21,14 +21,18 @@ #include #include extern "C" { - #pragma warning( push ) - #pragma warning( disable: 4201 ) - #include - #pragma warning( pop ) +#pragma warning( push ) +#pragma warning( disable: 4201 ) +#include +#pragma warning( pop ) } namespace GS { class Texture { + protected: + gs_texture_t * m_texture; + bool m_isOwner = true; + public: enum Type : uint8_t { Normal, @@ -54,7 +58,8 @@ namespace GS { * \param mip_data * \param flags */ - Texture(uint32_t width, uint32_t height, gs_color_format format, uint32_t mip_levels, const uint8_t **mip_data, uint32_t flags); + Texture(uint32_t width, uint32_t height, gs_color_format format, uint32_t mip_levels, + const uint8_t **mip_data, uint32_t flags); /*! * \brief Create a new volume texture from data @@ -69,7 +74,8 @@ namespace GS { * \param mip_data * \param flags */ - Texture(uint32_t width, uint32_t height, uint32_t depth, gs_color_format format, uint32_t mip_levels, const uint8_t **mip_data, uint32_t flags); + Texture(uint32_t width, uint32_t height, uint32_t depth, gs_color_format format, uint32_t mip_levels, + const uint8_t **mip_data, uint32_t flags); /*! * \brief Create a new cube texture from data @@ -82,7 +88,8 @@ namespace GS { * \param mip_data * \param flags */ - Texture(uint32_t size, gs_color_format format, uint32_t mip_levels, const uint8_t **mip_data, uint32_t flags); + Texture(uint32_t size, gs_color_format format, uint32_t mip_levels, const uint8_t **mip_data, + uint32_t flags); /*! * \brief Load a texture from a file @@ -94,7 +101,12 @@ namespace GS { * * \param file File to create the texture from. */ - Texture(std::string file);// { LoadFromFile(file); } + Texture(std::string file); + + /*! + * \brief Create a texture from an existing gs_texture_t object. + */ + Texture(gs_texture_t* tex, bool takeOwnership = false) : m_texture(tex), m_isOwner(takeOwnership) {} /*! * \brief Copy an existing texture @@ -139,8 +151,5 @@ namespace GS { * \return gs_texture_t* */ gs_texture_t* GetObject(); - - protected: - gs_texture_t* m_texture; }; } From cc68e2864fe4f58d6072ea71f2ce365c50f06c22 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 15:10:51 +0100 Subject: [PATCH 07/12] gs-rendertarget: Add and implement GetTexture(...) methods --- source/gs-rendertarget.cpp | 12 ++++++++++++ source/gs-rendertarget.h | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/source/gs-rendertarget.cpp b/source/gs-rendertarget.cpp index 65428740..8e5ce3f9 100644 --- a/source/gs-rendertarget.cpp +++ b/source/gs-rendertarget.cpp @@ -50,6 +50,18 @@ gs_texture_t* GS::RenderTarget::GetTextureObject() { return tex; } +void GS::RenderTarget::GetTexture(GS::Texture& tex) { + tex = GS::Texture(GetTextureObject(), false); +} + +void GS::RenderTarget::GetTexture(std::shared_ptr& tex) { + tex = std::make_shared(GetTextureObject(), false); +} + +void GS::RenderTarget::GetTexture(std::unique_ptr& tex) { + tex = std::make_unique(GetTextureObject(), false); +} + GS::RenderTargetOp::RenderTargetOp(GS::RenderTarget* rt, uint32_t width, uint32_t height) : m_renderTarget(rt) { if (m_renderTarget == nullptr) throw std::invalid_argument("rt"); diff --git a/source/gs-rendertarget.h b/source/gs-rendertarget.h index ce1c62c3..f866d835 100644 --- a/source/gs-rendertarget.h +++ b/source/gs-rendertarget.h @@ -19,6 +19,8 @@ #pragma once #include +#include +#include "gs-texture.h" extern "C" { #pragma warning( push ) #pragma warning( disable: 4201 ) @@ -35,6 +37,9 @@ namespace GS { virtual ~RenderTarget(); gs_texture_t* GetTextureObject(); + void GetTexture(GS::Texture& tex); + void GetTexture(std::shared_ptr& tex); + void GetTexture(std::unique_ptr& tex); GS::RenderTargetOp Render(uint32_t width, uint32_t height); protected: From 75aeb561bddda36ff5da6a04b9a79d40ef3e7e79 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 15:17:34 +0100 Subject: [PATCH 08/12] util: Add SourceTexture utility This utility class is used to quickly render and retrieve a Texture from a source. It should be used in place of manual rendering since it can be updated quickly, fixing outstanding issue in all places of the plugin instead of just one. --- CMakeLists.txt | 2 + source/util-source-texture.cpp | 70 ++++++++++++++++++++++++++++++++++ source/util-source-texture.h | 39 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 source/util-source-texture.cpp create mode 100644 source/util-source-texture.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f65534bb..e1c6e950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ SET(obs-stream-effects_HEADERS "${PROJECT_SOURCE_DIR}/source/strings.h" "${PROJECT_SOURCE_DIR}/source/util-math.h" "${PROJECT_SOURCE_DIR}/source/util-memory.h" + "${PROJECT_SOURCE_DIR}/source/util-source-texture.h" ) SET(obs-stream-effects_SOURCES "${PROJECT_SOURCE_DIR}/source/plugin.cpp" @@ -72,6 +73,7 @@ SET(obs-stream-effects_SOURCES "${PROJECT_SOURCE_DIR}/source/gs-vertexbuffer.cpp" "${PROJECT_SOURCE_DIR}/source/util-math.cpp" "${PROJECT_SOURCE_DIR}/source/util-memory.cpp" + "${PROJECT_SOURCE_DIR}/source/util-source-texture.cpp" ) SET(obs-stream-effects_LOCALE "${PROJECT_SOURCE_DIR}/data/locale/en-US.ini" diff --git a/source/util-source-texture.cpp b/source/util-source-texture.cpp new file mode 100644 index 00000000..d31dae48 --- /dev/null +++ b/source/util-source-texture.cpp @@ -0,0 +1,70 @@ +// 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 + +#include "util-source-texture.h" + +util::SourceTexture::~SourceTexture() { + if (m_source) { + obs_source_release(m_source); + m_source = nullptr; + } + m_rt = nullptr; +} + +util::SourceTexture::SourceTexture() { + m_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); +} + +util::SourceTexture::SourceTexture(const char* name) : SourceTexture() { + m_source = obs_get_source_by_name(name); + if (!m_source) { + throw std::invalid_argument("No such source."); + } +} + +util::SourceTexture::SourceTexture(std::string name) : SourceTexture(name.c_str()) {} + +util::SourceTexture::SourceTexture(obs_source_t* src) : SourceTexture() { + m_source = src; + if (!m_source) { + throw std::invalid_argument("No such source."); + } +} + +std::shared_ptr util::SourceTexture::Render(size_t width, size_t height) { + if (!m_source) { + throw std::invalid_argument("Missing source to render."); + } + if ((width == 0) || (width >= 16384)) { + throw std::runtime_error("Width too large or too small."); + } + if ((height == 0) || (height >= 16384)) { + throw std::runtime_error("Height too large or too small."); + } + + { + auto op = m_rt->Render((uint32_t)width, (uint32_t)height); + vec4 black; vec4_zero(&black); + gs_ortho(0, (float_t)width, 0, (float_t)height, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + obs_source_video_render(m_source); + } + + std::shared_ptr tex; + m_rt->GetTexture(tex); + return tex; +} diff --git a/source/util-source-texture.h b/source/util-source-texture.h new file mode 100644 index 00000000..695a7613 --- /dev/null +++ b/source/util-source-texture.h @@ -0,0 +1,39 @@ +// 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 + +#pragma once +#include +#include +#include +#include "gs-texture.h" +#include "gs-rendertarget.h" + +namespace util { + class SourceTexture { + obs_source_t* m_source; + std::shared_ptr m_rt; + + SourceTexture(); + public: + ~SourceTexture(); + SourceTexture(const char* name); + SourceTexture(std::string name); + SourceTexture(obs_source_t* src); + + std::shared_ptr Render(size_t width, size_t height); + }; +} From 224544effff084e95c088efd9e252c08fe23c3e6 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 16:19:42 +0100 Subject: [PATCH 09/12] util-source-texture: Add method to retrieve obs_source_t* obejct --- source/util-source-texture.cpp | 4 ++++ source/util-source-texture.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/source/util-source-texture.cpp b/source/util-source-texture.cpp index d31dae48..670ddbf5 100644 --- a/source/util-source-texture.cpp +++ b/source/util-source-texture.cpp @@ -29,6 +29,10 @@ util::SourceTexture::SourceTexture() { m_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); } +obs_source_t* util::SourceTexture::GetObject() { + return m_source; +} + util::SourceTexture::SourceTexture(const char* name) : SourceTexture() { m_source = obs_get_source_by_name(name); if (!m_source) { diff --git a/source/util-source-texture.h b/source/util-source-texture.h index 695a7613..9bc1f0b8 100644 --- a/source/util-source-texture.h +++ b/source/util-source-texture.h @@ -34,6 +34,8 @@ namespace util { SourceTexture(std::string name); SourceTexture(obs_source_t* src); + obs_source_t* GetObject(); + std::shared_ptr Render(size_t width, size_t height); }; } From 1f60e3d56f8694fd71f7a3007da7c53da05d372d Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 16:21:30 +0100 Subject: [PATCH 10/12] util-source-texture: Fix commit 75aeb561bddda36ff5da6a04b9a79d40ef3e7e79 Including obs.h directly is not compatible with CPack installations of libobs. --- source/util-source-texture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/util-source-texture.h b/source/util-source-texture.h index 9bc1f0b8..8422fe51 100644 --- a/source/util-source-texture.h +++ b/source/util-source-texture.h @@ -17,7 +17,7 @@ #pragma once #include -#include +#include #include #include "gs-texture.h" #include "gs-rendertarget.h" From 304db2333528c7fc6fb994f0d15fa6a7d490fb01 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 16:36:20 +0100 Subject: [PATCH 11/12] util-math: Add SizeFromString method Converts a String to an std::pair of int64_t (long long), which contain the size or 0 if none could be parsed. Any delimiter except digits(0-9), a minus sign(-) or a plus sign(+) between width and height are allowed. If a plus or minus sign is used as a delimiter, it must immediately be followed by the second size number. This allows for formats such as: 100x100, 100:100, 100p100, 100@100, 100+100 and so on, but not formats such as 100+:100, 100ThisIsSomeReall+yLongText100, etc. The parameter 'allowSquare' also determines what to do when the height parameter is not found. A value of true will have the function return instead of . --- source/util-math.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++-- source/util-math.h | 4 ++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/source/util-math.cpp b/source/util-math.cpp index 836e3391..cc69e742 100644 --- a/source/util-math.cpp +++ b/source/util-math.cpp @@ -20,8 +20,10 @@ #include "util-math.h" #include "util-memory.h" #include +#include +#include -void* util::vec3a::operator new(size_t count){ +void* util::vec3a::operator new(size_t count) { return _aligned_malloc(count, 16); } @@ -37,7 +39,7 @@ void util::vec3a::operator delete[](void* p) { _aligned_free(p); } -void* util::vec4a::operator new(size_t count){ +void* util::vec4a::operator new(size_t count) { return _aligned_malloc(count, 16); } @@ -52,3 +54,42 @@ void util::vec4a::operator delete(void* p) { void util::vec4a::operator delete[](void* p) { _aligned_free(p); } + +std::pair util::SizeFromString(std::string text, bool allowSquare) { + int64_t width, height; + + const char* begin = text.c_str(); + const char* end = text.c_str() + text.size() + 1; + char* here = const_cast(end); + + long long res = strtoll(begin, &here, 0); + if (errno == ERANGE) { + return { 0, 0 }; + } + width = res; + + while (here != end) { + if (isdigit(*here) || (*here == '-') || (*here == '+')) { + break; + } + here++; + } + if (here == end) { + // Are we allowed to return a square? + if (allowSquare) { + // Yes: Return width,width. + return { width, width }; + } else { + // No: Return width,0. + return { width, 0 }; + } + } + + res = strtoll(here, nullptr, 0); + if (errno == ERANGE) { + return { width, 0 }; + } + height = res; + + return { width, height }; +} diff --git a/source/util-math.h b/source/util-math.h index 479f4c1e..53f9b145 100644 --- a/source/util-math.h +++ b/source/util-math.h @@ -20,6 +20,8 @@ #pragma once #include #include +#include +#include // OBS #include @@ -60,4 +62,6 @@ namespace util { static void vec4a::operator delete(void* p); static void vec4a::operator delete[](void* p); }; + + std::pair SizeFromString(std::string text, bool allowSquare = true); } From 65a7ac696b0d72680f9e6c239e5cd52cca228ea9 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Mon, 5 Mar 2018 16:50:19 +0100 Subject: [PATCH 12/12] source-mirror: Use util:SourceTexture instead of custom code --- source/source-mirror.cpp | 56 +++++++++++++++++----------------------- source/source-mirror.h | 34 ++++++++++++------------ 2 files changed, 40 insertions(+), 50 deletions(-) diff --git a/source/source-mirror.cpp b/source/source-mirror.cpp index af69beb0..0a1ef78b 100644 --- a/source/source-mirror.cpp +++ b/source/source-mirror.cpp @@ -200,11 +200,9 @@ void Source::MirrorAddon::video_render(void *p, gs_effect_t *ef) { Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) { m_active = true; m_source = src; - m_target = nullptr; m_rescale = false; m_width = m_height = 1; - m_renderTarget = std::make_unique(GS_RGBA, GS_ZS_NONE); m_renderTargetScale = std::make_unique(GS_RGBA, GS_ZS_NONE); m_sampler = std::make_shared(); m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); @@ -212,34 +210,34 @@ Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) { update(data); } -Source::Mirror::~Mirror() { - if (m_target) - obs_source_release(m_target); -} +Source::Mirror::~Mirror() {} uint32_t Source::Mirror::get_width() { if (m_rescale && m_width > 0 && !m_keepOriginalSize) return m_width; - if (m_target && m_target != m_source) - return obs_source_get_width(m_target); + if (m_mirrorSource) + return obs_source_get_width(m_mirrorSource->GetObject()); return 1; } uint32_t Source::Mirror::get_height() { if (m_rescale && m_height > 0 && !m_keepOriginalSize) return m_height; - if (m_target && m_target != m_source) - return obs_source_get_height(m_target); + if (m_mirrorSource) + return obs_source_get_height(m_mirrorSource->GetObject()); return 1; } void Source::Mirror::update(obs_data_t* data) { // Update selected source. - const char* source = obs_data_get_string(data, P_SOURCE); - if (m_target) - obs_source_release(m_target); - m_target = obs_get_source_by_name(source); - m_targetName = source; + const char* sourceName = obs_data_get_string(data, P_SOURCE); + if (sourceName != m_mirrorName) { + try { + m_mirrorSource = std::make_unique(sourceName); + m_mirrorName = sourceName; + } catch (...) { + } + } // Rescaling m_rescale = obs_data_get_bool(data, P_SCALING); @@ -310,30 +308,22 @@ void Source::Mirror::deactivate() { } void Source::Mirror::video_tick(float) { - if (!m_target) - m_target = obs_get_source_by_name(m_targetName.c_str()); + if (m_mirrorSource) + m_mirrorName = obs_source_get_name(m_mirrorSource->GetObject()); } void Source::Mirror::video_render(gs_effect_t* effect) { - if (!m_active || (m_source == m_target) || (m_width == 0 || m_height == 0)) + if ((m_width == 0) || (m_height == 0) || !m_mirrorSource) { return; + } if (m_rescale && m_width > 0 && m_height > 0 && m_scalingEffect) { uint32_t sw, sh; - sw = obs_source_get_width(m_target); - sh = obs_source_get_height(m_target); + sw = obs_source_get_width(m_mirrorSource->GetObject()); + sh = obs_source_get_height(m_mirrorSource->GetObject()); // Store original Source Texture - try { - vec4 black; vec4_zero(&black); - auto op = m_renderTarget->Render(sw, sh); - gs_ortho(0, (float_t)sw, 0, (float_t)sh, 0, 1); - gs_clear(GS_CLEAR_COLOR, &black, 0, 0); - - obs_source_video_render(m_target); - } catch (...) { - return; - } + std::shared_ptr tex = m_mirrorSource->Render(sw, sh); gs_eparam_t *scale_param = gs_effect_get_param_by_name(m_scalingEffect, "base_dimension_i"); if (scale_param) { @@ -353,7 +343,7 @@ void Source::Mirror::video_render(gs_effect_t* effect) { while (gs_effect_loop(m_scalingEffect, "Draw")) { gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image"); gs_effect_set_next_sampler(image, m_sampler->GetObject()); - obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, m_width, m_height, false); + obs_source_draw(tex->GetObject(), 0, 0, m_width, m_height, false); } } while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { @@ -365,10 +355,10 @@ void Source::Mirror::video_render(gs_effect_t* effect) { while (gs_effect_loop(m_scalingEffect, "Draw")) { gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image"); gs_effect_set_next_sampler(image, m_sampler->GetObject()); - obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, m_width, m_height, false); + obs_source_draw(tex->GetObject(), 0, 0, m_width, m_height, false); } } } else { - obs_source_video_render(m_target); + obs_source_video_render(m_mirrorSource->GetObject()); } } diff --git a/source/source-mirror.h b/source/source-mirror.h index deb4f337..2fb9e461 100644 --- a/source/source-mirror.h +++ b/source/source-mirror.h @@ -21,10 +21,13 @@ #include "plugin.h" #include "gs-rendertarget.h" #include "gs-sampler.h" +#include "util-source-texture.h" #include namespace Source { class MirrorAddon { + obs_source_info osi; + public: MirrorAddon(); ~MirrorAddon(); @@ -45,12 +48,23 @@ namespace Source { static void deactivate(void *); static void video_tick(void *, float); static void video_render(void *, gs_effect_t *); - - private: - obs_source_info osi; }; class Mirror { + bool m_active; + obs_source_t* m_source = nullptr; + + + bool m_rescale = false; + bool m_keepOriginalSize = false; + uint32_t m_width, m_height; + std::unique_ptr m_renderTargetScale; + std::shared_ptr m_sampler; + gs_effect_t* m_scalingEffect = nullptr; + + std::unique_ptr m_mirrorSource; + std::string m_mirrorName; + public: Mirror(obs_data_t*, obs_source_t*); ~Mirror(); @@ -63,19 +77,5 @@ namespace Source { void deactivate(); void video_tick(float); void video_render(gs_effect_t*); - - private: - bool m_active; - obs_source_t* m_source = nullptr; - obs_source_t* m_target = nullptr; - std::string m_targetName; - - bool m_rescale = false; - bool m_keepOriginalSize = false; - uint32_t m_width, m_height; - std::unique_ptr m_renderTarget; - std::unique_ptr m_renderTargetScale; - std::shared_ptr m_sampler; - gs_effect_t* m_scalingEffect = nullptr; }; };