diff --git a/CMakeLists.txt b/CMakeLists.txt index 78c6eed5..9d3093b7 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" @@ -51,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" @@ -58,6 +60,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" @@ -70,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/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..f06db8a9 --- /dev/null +++ b/source/filter-custom-shader.cpp @@ -0,0 +1,766 @@ +/* + * 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 +#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 "" + +/** 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). + * - 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, + 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_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, + 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 = obs_module_file("effects/displace.effect"); + if (ptr) + 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()); + + if (ptr) + reinterpret_cast(ptr)->get_properties(pr); + + return pr; +} + +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: + 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; + } + + 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; +} + +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_source = source; + + 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); +} + +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.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()); + } + } else if (shaderType == ShaderType::File) { + CheckShaderFile(obs_data_get_string(data, S_CONTENT_FILE), 0.0f); + } + + m_effectParameters.clear(); + 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; + + 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] = (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] = (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] = (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] = (float_t)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] = (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] = (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] = (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] = (int32_t)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; + } + } +} + +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; + m_activeTime = 0.0f; +} + +void Filter::CustomShader::Instance::deactivate() { + m_isActive = false; +} + +void Filter::CustomShader::Instance::video_tick(float 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) { + if (!m_source || !m_isActive) { + obs_source_skip_video_filter(m_source); + return; + } + + 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.effect) { + 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_source); + return; + } + + // 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); + } + } + 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.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 && 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); + { + 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(m_effect.effect->GetObject(), "Draw")) { + obs_source_draw(sourceTexture, 0, 0, baseW, baseH, false); + } +} + +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.effect) + return; + + for (Parameter& prm : m_effectParameters) { + switch (prm.type) { + case GS::EffectParameter::Type::Boolean: + 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: + 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: + 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::CheckShaderFile(std::string file, float_t time) { + bool doRefresh = false; + + // Update if the paths are different. + if (file != m_effect.path) { + m_effect.path = file; + doRefresh = true; + } + + if (file.empty()) { + m_effect.effect = nullptr; + return; + } + + // 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_effect.path.c_str(), &stats) == 0) { + doRefresh = doRefresh || + (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 { + 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()); + } + } +} + +std::string Filter::CustomShader::Instance::GetShaderFile() { + 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 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) { + 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(prm.value.file.path.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) { + prm.value.file.dirty = false; + try { + 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()); + } + } + } + } +} + +bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { + std::pair reservedParameters[] = { + { "ViewProj", GS::EffectParameter::Type::Matrix }, + { "ViewSize", GS::EffectParameter::Type::Float2 }, + { "ViewSizeI", GS::EffectParameter::Type::Integer2 }, + { "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.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 new file mode 100644 index 00000000..2d8e9766 --- /dev/null +++ b/source/filter-custom-shader.h @@ -0,0 +1,126 @@ +/* + * 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 "gs-rendertarget.h" +#include +#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 *); + + 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); + + private: + obs_source_t * m_source; + bool m_isActive = true; + std::unique_ptr m_renderTarget; + + float_t m_activeTime, m_renderTime; + + // Shader + struct Effect { + std::string path; + time_t createTime, modifiedTime; + size_t size; + float_t lastCheck; + std::unique_ptr effect; + } m_effect; + + struct Parameter { + std::string name; + GS::EffectParameter::Type type; + + std::vector uiNames; + std::vector uiDescriptions; + + struct { + union { + int32_t i[4]; + float_t f[4]; + bool b; + }; + bool textureIsSource = false; + struct Source { + bool dirty = false; + std::string name; + obs_source_t* source = nullptr; + std::shared_ptr rendertarget; + } source; + struct File { + 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; + + friend class CustomShader; + }; + }; +} 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; 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: 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; }; } 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; }; }; 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); } 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 } diff --git a/source/util-source-texture.cpp b/source/util-source-texture.cpp new file mode 100644 index 00000000..670ddbf5 --- /dev/null +++ b/source/util-source-texture.cpp @@ -0,0 +1,74 @@ +// 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); +} + +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) { + 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..8422fe51 --- /dev/null +++ b/source/util-source-texture.h @@ -0,0 +1,41 @@ +// 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); + + obs_source_t* GetObject(); + + std::shared_ptr Render(size_t width, size_t height); + }; +}