Merge branch 'filter-custom-shader'

This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2018-03-05 16:50:57 +01:00
commit 53073660c0
17 changed files with 1199 additions and 79 deletions

View file

@ -36,6 +36,7 @@ SET(obs-stream-effects_HEADERS
"${PROJECT_SOURCE_DIR}/source/filter-blur.h" "${PROJECT_SOURCE_DIR}/source/filter-blur.h"
"${PROJECT_SOURCE_DIR}/source/filter-shape.h" "${PROJECT_SOURCE_DIR}/source/filter-shape.h"
"${PROJECT_SOURCE_DIR}/source/filter-transform.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/source-mirror.h"
"${PROJECT_SOURCE_DIR}/source/gs-helper.h" "${PROJECT_SOURCE_DIR}/source/gs-helper.h"
"${PROJECT_SOURCE_DIR}/source/gs-effect.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/strings.h"
"${PROJECT_SOURCE_DIR}/source/util-math.h" "${PROJECT_SOURCE_DIR}/source/util-math.h"
"${PROJECT_SOURCE_DIR}/source/util-memory.h" "${PROJECT_SOURCE_DIR}/source/util-memory.h"
"${PROJECT_SOURCE_DIR}/source/util-source-texture.h"
) )
SET(obs-stream-effects_SOURCES SET(obs-stream-effects_SOURCES
"${PROJECT_SOURCE_DIR}/source/plugin.cpp" "${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-blur.cpp"
"${PROJECT_SOURCE_DIR}/source/filter-shape.cpp" "${PROJECT_SOURCE_DIR}/source/filter-shape.cpp"
"${PROJECT_SOURCE_DIR}/source/filter-transform.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/source-mirror.cpp"
"${PROJECT_SOURCE_DIR}/source/gs-helper.cpp" "${PROJECT_SOURCE_DIR}/source/gs-helper.cpp"
"${PROJECT_SOURCE_DIR}/source/gs-effect.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/gs-vertexbuffer.cpp"
"${PROJECT_SOURCE_DIR}/source/util-math.cpp" "${PROJECT_SOURCE_DIR}/source/util-math.cpp"
"${PROJECT_SOURCE_DIR}/source/util-memory.cpp" "${PROJECT_SOURCE_DIR}/source/util-memory.cpp"
"${PROJECT_SOURCE_DIR}/source/util-source-texture.cpp"
) )
SET(obs-stream-effects_LOCALE SET(obs-stream-effects_LOCALE
"${PROJECT_SOURCE_DIR}/data/locale/en-US.ini" "${PROJECT_SOURCE_DIR}/data/locale/en-US.ini"

View file

@ -14,6 +14,14 @@ Filter.Blur.Bilateral.Smoothing="Smoothing"
Filter.Blur.Bilateral.Sharpness="Sharpness" Filter.Blur.Bilateral.Sharpness="Sharpness"
Filter.Blur.ColorFormat="Color Format" 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
Filter.Displacement="Displacement Mapping" Filter.Displacement="Displacement Mapping"
Filter.Displacement.File="File" Filter.Displacement.File="File"

View file

@ -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 <vector>
#include <tuple>
#include <fstream>
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<Instance*>(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<Instance*>(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<Instance*>(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<Instance*>(ptr);
}
uint32_t Filter::CustomShader::get_width(void *ptr) {
return reinterpret_cast<Instance*>(ptr)->get_width();
}
uint32_t Filter::CustomShader::get_height(void *ptr) {
return reinterpret_cast<Instance*>(ptr)->get_height();
}
void Filter::CustomShader::update(void *ptr, obs_data_t *data) {
reinterpret_cast<Instance*>(ptr)->update(data);
}
void Filter::CustomShader::activate(void *ptr) {
reinterpret_cast<Instance*>(ptr)->activate();
}
void Filter::CustomShader::deactivate(void *ptr) {
reinterpret_cast<Instance*>(ptr)->activate();
}
void Filter::CustomShader::video_tick(void *ptr, float time) {
reinterpret_cast<Instance*>(ptr)->video_tick(time);
}
void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) {
reinterpret_cast<Instance*>(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::RenderTarget>(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<GS::Effect>(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<char> 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<GS::Effect>(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::RenderTarget>(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<GS::Texture>(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<std::string, GS::EffectParameter::Type> 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<std::string, GS::EffectParameter::Type> 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;
}

View file

@ -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 <list>
#include <vector>
#include <inttypes.h>
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<GS::RenderTarget> 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<GS::Effect> effect;
} m_effect;
struct Parameter {
std::string name;
GS::EffectParameter::Type type;
std::vector<std::string> uiNames;
std::vector<std::string> 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<GS::RenderTarget> rendertarget;
} source;
struct File {
bool dirty = false;
std::string path;
time_t createTime, modifiedTime;
size_t fileSize;
float_t lastCheck;
std::shared_ptr<GS::Texture> texture;
} file;
} value;
};
std::list<Parameter> m_effectParameters;
friend class CustomShader;
};
};
}

View file

@ -30,7 +30,7 @@ GS::Effect::Effect() {
m_effect = nullptr; m_effect = nullptr;
} }
GS::Effect::Effect(std::string file) { GS::Effect::Effect(std::string file) {
obs_enter_graphics(); obs_enter_graphics();
char* errorMessage = nullptr; char* errorMessage = nullptr;
m_effect = gs_effect_create_from_file(file.c_str(), &errorMessage); 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); 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::EffectParameter GS::Effect::GetParameter(std::string name) {
gs_eparam_t* param = gs_effect_get_param_by_name(m_effect, name.c_str()); gs_eparam_t* param = gs_effect_get_param_by_name(m_effect, name.c_str());
if (!param) if (!param)
@ -170,8 +183,8 @@ void GS::EffectParameter::SetFloat2(vec2& v) {
void GS::EffectParameter::SetFloat2(float_t x, float_t y) { void GS::EffectParameter::SetFloat2(float_t x, float_t y) {
if (GetType() != Type::Float2) if (GetType() != Type::Float2)
throw std::bad_cast(); throw std::bad_cast();
float_t v[] = { x, y }; vec2 v = { x, y };
SetFloatArray(v, 2); gs_effect_set_vec2(m_param, &v);
} }
void GS::EffectParameter::SetFloat3(vec3& 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) { void GS::EffectParameter::SetFloat3(float_t x, float_t y, float_t z) {
if (GetType() != Type::Float3) if (GetType() != Type::Float3)
throw std::bad_cast(); throw std::bad_cast();
float_t v[] = { x, y, z }; vec3 v = { x, y, z };
SetFloatArray(v, 3); gs_effect_set_vec3(m_param, &v);
} }
void GS::EffectParameter::SetFloat4(vec4& 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) { void GS::EffectParameter::SetFloat4(float_t x, float_t y, float_t z, float_t w) {
if (GetType() != Type::Float4) if (GetType() != Type::Float4)
throw std::bad_cast(); throw std::bad_cast();
float_t v[] = { x, y, z, w }; vec4 v = { x, y, z, w };
SetFloatArray(v, 4); gs_effect_set_vec4(m_param, &v);
} }
void GS::EffectParameter::SetFloatArray(float_t v[], size_t sz) { void GS::EffectParameter::SetFloatArray(float_t v[], size_t sz) {
if ((GetType() != Type::Float) && (GetType() != Type::Float2) && (GetType() != Type::Float3) && (GetType() != Type::Float4)) if ((GetType() != Type::Float) && (GetType() != Type::Float2) && (GetType() != Type::Float3) && (GetType() != Type::Float4))
throw std::bad_cast(); 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) { 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) if (GetType() != Type::Integer2)
throw std::bad_cast(); throw std::bad_cast();
int32_t v[] = { x, y }; 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) { void GS::EffectParameter::SetInteger3(int32_t x, int32_t y, int32_t z) {
if (GetType() != Type::Integer3) if (GetType() != Type::Integer3)
throw std::bad_cast(); throw std::bad_cast();
int32_t v[] = { x, y, z }; 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) { void GS::EffectParameter::SetInteger4(int32_t x, int32_t y, int32_t z, int32_t w) {
if (GetType() != Type::Integer4) if (GetType() != Type::Integer4)
throw std::bad_cast(); throw std::bad_cast();
int32_t v[] = { x, y, z, w }; 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) { void GS::EffectParameter::SetIntegerArray(int32_t v[], size_t sz) {
if ((GetType() != Type::Integer) && (GetType() != Type::Integer2) && (GetType() != Type::Integer3) && (GetType() != Type::Integer4)) if ((GetType() != Type::Integer) && (GetType() != Type::Integer2) && (GetType() != Type::Integer3) && (GetType() != Type::Integer4))
throw std::bad_cast(); 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) { void GS::EffectParameter::SetMatrix(matrix4& v) {

View file

@ -99,6 +99,8 @@ namespace GS {
std::list<EffectParameter> GetParameters(); std::list<EffectParameter> GetParameters();
EffectParameter GetParameter(size_t idx); EffectParameter GetParameter(size_t idx);
EffectParameter GetParameter(std::string name); EffectParameter GetParameter(std::string name);
bool HasParameter(std::string name);
bool HasParameter(std::string name, EffectParameter::Type type);
protected: protected:
gs_effect_t* m_effect; gs_effect_t* m_effect;

View file

@ -50,6 +50,18 @@ gs_texture_t* GS::RenderTarget::GetTextureObject() {
return tex; return tex;
} }
void GS::RenderTarget::GetTexture(GS::Texture& tex) {
tex = GS::Texture(GetTextureObject(), false);
}
void GS::RenderTarget::GetTexture(std::shared_ptr<GS::Texture>& tex) {
tex = std::make_shared<GS::Texture>(GetTextureObject(), false);
}
void GS::RenderTarget::GetTexture(std::unique_ptr<GS::Texture>& tex) {
tex = std::make_unique<GS::Texture>(GetTextureObject(), false);
}
GS::RenderTargetOp::RenderTargetOp(GS::RenderTarget* rt, uint32_t width, uint32_t height) : m_renderTarget(rt) { GS::RenderTargetOp::RenderTargetOp(GS::RenderTarget* rt, uint32_t width, uint32_t height) : m_renderTarget(rt) {
if (m_renderTarget == nullptr) if (m_renderTarget == nullptr)
throw std::invalid_argument("rt"); throw std::invalid_argument("rt");

View file

@ -19,6 +19,8 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <memory>
#include "gs-texture.h"
extern "C" { extern "C" {
#pragma warning( push ) #pragma warning( push )
#pragma warning( disable: 4201 ) #pragma warning( disable: 4201 )
@ -35,6 +37,9 @@ namespace GS {
virtual ~RenderTarget(); virtual ~RenderTarget();
gs_texture_t* GetTextureObject(); gs_texture_t* GetTextureObject();
void GetTexture(GS::Texture& tex);
void GetTexture(std::shared_ptr<GS::Texture>& tex);
void GetTexture(std::unique_ptr<GS::Texture>& tex);
GS::RenderTargetOp Render(uint32_t width, uint32_t height); GS::RenderTargetOp Render(uint32_t width, uint32_t height);
protected: protected:

View file

@ -178,7 +178,7 @@ GS::Texture::Texture(Texture& other) {
} }
GS::Texture::~Texture() { GS::Texture::~Texture() {
if (m_texture) { if (m_isOwner && m_texture) {
obs_enter_graphics(); obs_enter_graphics();
switch (gs_get_texture_type(m_texture)) { switch (gs_get_texture_type(m_texture)) {
case GS_TEXTURE_2D: case GS_TEXTURE_2D:

View file

@ -21,14 +21,18 @@
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
extern "C" { extern "C" {
#pragma warning( push ) #pragma warning( push )
#pragma warning( disable: 4201 ) #pragma warning( disable: 4201 )
#include <libobs/graphics/graphics.h> #include <libobs/graphics/graphics.h>
#pragma warning( pop ) #pragma warning( pop )
} }
namespace GS { namespace GS {
class Texture { class Texture {
protected:
gs_texture_t * m_texture;
bool m_isOwner = true;
public: public:
enum Type : uint8_t { enum Type : uint8_t {
Normal, Normal,
@ -54,7 +58,8 @@ namespace GS {
* \param mip_data * \param mip_data
* \param flags * \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 * \brief Create a new volume texture from data
@ -69,7 +74,8 @@ namespace GS {
* \param mip_data * \param mip_data
* \param flags * \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 * \brief Create a new cube texture from data
@ -82,7 +88,8 @@ namespace GS {
* \param mip_data * \param mip_data
* \param flags * \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 * \brief Load a texture from a file
@ -94,7 +101,12 @@ namespace GS {
* *
* \param file File to create the texture from. * \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 * \brief Copy an existing texture
@ -139,8 +151,5 @@ namespace GS {
* \return gs_texture_t* * \return gs_texture_t*
*/ */
gs_texture_t* GetObject(); gs_texture_t* GetObject();
protected:
gs_texture_t* m_texture;
}; };
} }

View file

@ -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) { Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) {
m_active = true; m_active = true;
m_source = src; m_source = src;
m_target = nullptr;
m_rescale = false; m_rescale = false;
m_width = m_height = 1; m_width = m_height = 1;
m_renderTarget = std::make_unique<GS::RenderTarget>(GS_RGBA, GS_ZS_NONE);
m_renderTargetScale = std::make_unique<GS::RenderTarget>(GS_RGBA, GS_ZS_NONE); m_renderTargetScale = std::make_unique<GS::RenderTarget>(GS_RGBA, GS_ZS_NONE);
m_sampler = std::make_shared<GS::Sampler>(); m_sampler = std::make_shared<GS::Sampler>();
m_scalingEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); 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); update(data);
} }
Source::Mirror::~Mirror() { Source::Mirror::~Mirror() {}
if (m_target)
obs_source_release(m_target);
}
uint32_t Source::Mirror::get_width() { uint32_t Source::Mirror::get_width() {
if (m_rescale && m_width > 0 && !m_keepOriginalSize) if (m_rescale && m_width > 0 && !m_keepOriginalSize)
return m_width; return m_width;
if (m_target && m_target != m_source) if (m_mirrorSource)
return obs_source_get_width(m_target); return obs_source_get_width(m_mirrorSource->GetObject());
return 1; return 1;
} }
uint32_t Source::Mirror::get_height() { uint32_t Source::Mirror::get_height() {
if (m_rescale && m_height > 0 && !m_keepOriginalSize) if (m_rescale && m_height > 0 && !m_keepOriginalSize)
return m_height; return m_height;
if (m_target && m_target != m_source) if (m_mirrorSource)
return obs_source_get_height(m_target); return obs_source_get_height(m_mirrorSource->GetObject());
return 1; return 1;
} }
void Source::Mirror::update(obs_data_t* data) { void Source::Mirror::update(obs_data_t* data) {
// Update selected source. // Update selected source.
const char* source = obs_data_get_string(data, P_SOURCE); const char* sourceName = obs_data_get_string(data, P_SOURCE);
if (m_target) if (sourceName != m_mirrorName) {
obs_source_release(m_target); try {
m_target = obs_get_source_by_name(source); m_mirrorSource = std::make_unique<util::SourceTexture>(sourceName);
m_targetName = source; m_mirrorName = sourceName;
} catch (...) {
}
}
// Rescaling // Rescaling
m_rescale = obs_data_get_bool(data, P_SCALING); m_rescale = obs_data_get_bool(data, P_SCALING);
@ -310,30 +308,22 @@ void Source::Mirror::deactivate() {
} }
void Source::Mirror::video_tick(float) { void Source::Mirror::video_tick(float) {
if (!m_target) if (m_mirrorSource)
m_target = obs_get_source_by_name(m_targetName.c_str()); m_mirrorName = obs_source_get_name(m_mirrorSource->GetObject());
} }
void Source::Mirror::video_render(gs_effect_t* effect) { 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; return;
}
if (m_rescale && m_width > 0 && m_height > 0 && m_scalingEffect) { if (m_rescale && m_width > 0 && m_height > 0 && m_scalingEffect) {
uint32_t sw, sh; uint32_t sw, sh;
sw = obs_source_get_width(m_target); sw = obs_source_get_width(m_mirrorSource->GetObject());
sh = obs_source_get_height(m_target); sh = obs_source_get_height(m_mirrorSource->GetObject());
// Store original Source Texture // Store original Source Texture
try { std::shared_ptr<GS::Texture> tex = m_mirrorSource->Render(sw, sh);
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;
}
gs_eparam_t *scale_param = gs_effect_get_param_by_name(m_scalingEffect, "base_dimension_i"); gs_eparam_t *scale_param = gs_effect_get_param_by_name(m_scalingEffect, "base_dimension_i");
if (scale_param) { if (scale_param) {
@ -353,7 +343,7 @@ void Source::Mirror::video_render(gs_effect_t* effect) {
while (gs_effect_loop(m_scalingEffect, "Draw")) { while (gs_effect_loop(m_scalingEffect, "Draw")) {
gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image"); gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image");
gs_effect_set_next_sampler(image, m_sampler->GetObject()); 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")) { 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")) { while (gs_effect_loop(m_scalingEffect, "Draw")) {
gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image"); gs_eparam_t* image = gs_effect_get_param_by_name(m_scalingEffect, "image");
gs_effect_set_next_sampler(image, m_sampler->GetObject()); 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 { } else {
obs_source_video_render(m_target); obs_source_video_render(m_mirrorSource->GetObject());
} }
} }

View file

@ -21,10 +21,13 @@
#include "plugin.h" #include "plugin.h"
#include "gs-rendertarget.h" #include "gs-rendertarget.h"
#include "gs-sampler.h" #include "gs-sampler.h"
#include "util-source-texture.h"
#include <memory> #include <memory>
namespace Source { namespace Source {
class MirrorAddon { class MirrorAddon {
obs_source_info osi;
public: public:
MirrorAddon(); MirrorAddon();
~MirrorAddon(); ~MirrorAddon();
@ -45,12 +48,23 @@ namespace Source {
static void deactivate(void *); static void deactivate(void *);
static void video_tick(void *, float); static void video_tick(void *, float);
static void video_render(void *, gs_effect_t *); static void video_render(void *, gs_effect_t *);
private:
obs_source_info osi;
}; };
class Mirror { 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<GS::RenderTarget> m_renderTargetScale;
std::shared_ptr<GS::Sampler> m_sampler;
gs_effect_t* m_scalingEffect = nullptr;
std::unique_ptr<util::SourceTexture> m_mirrorSource;
std::string m_mirrorName;
public: public:
Mirror(obs_data_t*, obs_source_t*); Mirror(obs_data_t*, obs_source_t*);
~Mirror(); ~Mirror();
@ -63,19 +77,5 @@ namespace Source {
void deactivate(); void deactivate();
void video_tick(float); void video_tick(float);
void video_render(gs_effect_t*); 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<GS::RenderTarget> m_renderTarget;
std::unique_ptr<GS::RenderTarget> m_renderTargetScale;
std::shared_ptr<GS::Sampler> m_sampler;
gs_effect_t* m_scalingEffect = nullptr;
}; };
}; };

View file

@ -20,8 +20,10 @@
#include "util-math.h" #include "util-math.h"
#include "util-memory.h" #include "util-memory.h"
#include <malloc.h> #include <malloc.h>
#include <stdlib.h>
#include <cctype>
void* util::vec3a::operator new(size_t count){ void* util::vec3a::operator new(size_t count) {
return _aligned_malloc(count, 16); return _aligned_malloc(count, 16);
} }
@ -37,7 +39,7 @@ void util::vec3a::operator delete[](void* p) {
_aligned_free(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); return _aligned_malloc(count, 16);
} }
@ -52,3 +54,42 @@ void util::vec4a::operator delete(void* p) {
void util::vec4a::operator delete[](void* p) { void util::vec4a::operator delete[](void* p) {
_aligned_free(p); _aligned_free(p);
} }
std::pair<int64_t, int64_t> 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<char*>(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 };
}

View file

@ -20,6 +20,8 @@
#pragma once #pragma once
#include <math.h> #include <math.h>
#include <inttypes.h> #include <inttypes.h>
#include <utility>
#include <string>
// OBS // OBS
#include <libobs/graphics/vec2.h> #include <libobs/graphics/vec2.h>
@ -60,4 +62,6 @@ namespace util {
static void vec4a::operator delete(void* p); static void vec4a::operator delete(void* p);
static void vec4a::operator delete[](void* p); static void vec4a::operator delete[](void* p);
}; };
std::pair<int64_t, int64_t> SizeFromString(std::string text, bool allowSquare = true);
} }

View file

@ -19,25 +19,50 @@
#include "util-memory.h" #include "util-memory.h"
#include <cstdlib> #include <cstdlib>
#define USE_STD_ALLOC_FREE
void* util::malloc_aligned(size_t align, size_t size) { 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. // 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. // Allocate memory and store integer representation of pointer.
void* ptr = malloc(asize); void* ptr = malloc(asize);
// Calculate actual aligned position // Calculate actual aligned position
intptr_t ptr_off = aligned_offset(align, reinterpret_cast<size_t>(ptr)+sizeof(void*)); intptr_t ptr_off = aligned_offset(align, reinterpret_cast<size_t>(ptr) + sizeof(void*));
// Store actual pointer at ptr_off - sizeof(void*). // Store actual pointer at ptr_off - sizeof(void*).
*reinterpret_cast<intptr_t*>(ptr_off - sizeof(void*)) = reinterpret_cast<intptr_t>(ptr); *reinterpret_cast<intptr_t*>(ptr_off - sizeof(void*)) = reinterpret_cast<intptr_t>(ptr);
// Return aligned pointer // Return aligned pointer
return reinterpret_cast<void*>(ptr_off); return reinterpret_cast<void*>(ptr_off);
#endif
} }
void util::free_aligned(void* mem) { void util::free_aligned(void* mem) {
void* ptr = reinterpret_cast<void*>(*reinterpret_cast<intptr_t*>(static_cast<char*>(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<void*>(*reinterpret_cast<intptr_t*>(static_cast<char*>(mem) - sizeof(void*)));
free(ptr); free(ptr);
#endif
} }

View file

@ -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::RenderTarget>(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<GS::Texture> 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<GS::Texture> tex;
m_rt->GetTexture(tex);
return tex;
}

View file

@ -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 <string>
#include <libobs/obs.h>
#include <memory>
#include "gs-texture.h"
#include "gs-rendertarget.h"
namespace util {
class SourceTexture {
obs_source_t* m_source;
std::shared_ptr<GS::RenderTarget> 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<GS::Texture> Render(size_t width, size_t height);
};
}