filter-custom-shader: Implement new Filter 'Custom Shader'

Custom Shader allows you to write your own effect files and just have them applied to your source(s). It will dynamically update the properties to match the parameters in the source as well as offer some special parameters to the shader.

# Conflicts:
#	CMakeLists.txt
#	data/locale/en-US.ini

# Conflicts:
#	data/locale/en-US.ini
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2017-11-06 12:36:01 +01:00
parent adafbf40ca
commit 26b447901c
4 changed files with 530 additions and 0 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"
@ -58,6 +59,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"

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,409 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2017 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "filter-custom-shader.h"
#include "strings.h"
#include <vector>
#include <tuple>
extern "C" {
#pragma warning (push)
#pragma warning (disable: 4201)
#include "libobs/util/platform.h"
#include "libobs/graphics/graphics.h"
#include "libobs/graphics/matrix4.h"
#pragma warning (pop)
}
#define S "Filter.CustomShader"
#define S_TYPE "Filter.CustomShader.Type"
#define S_TYPE_TEXT "Filter.CustomShader.Type.Text"
#define S_TYPE_FILE "Filter.CustomShader.Type.File"
#define S_CONTENT_TEXT "Filter.CustomShader.Content.Text"
#define S_CONTENT_FILE "Filter.CustomShader.Content.File"
#define DEFAULT_SHADER ""
/* Special Parameters
Default:
- ViewSize - View Resolution, float2
- ViewSizeI - View Resolution, integer2
- Pass - Current Pass Index, integer
Texture2d:
- $(param)_size - Texture Size, float2
- $(param)_isize - Texture Size, integer2
- $(param)_texel - Texture Texel Size, float2
*/
enum class ShaderType : int64_t {
Text,
File
};
static Filter::CustomShader* handler;
INITIALIZER(HandlerInit) {
initializerFunctions.push_back([] {
handler = new Filter::CustomShader();
});
finalizerFunctions.push_back([] {
delete handler;
});
}
Filter::CustomShader::CustomShader() {
memset(&sourceInfo, 0, sizeof(obs_source_info));
sourceInfo.id = "obs-stream-effects-filter-custom-shader";
sourceInfo.type = OBS_SOURCE_TYPE_FILTER;
sourceInfo.output_flags = OBS_SOURCE_VIDEO;
sourceInfo.get_name = get_name;
sourceInfo.get_defaults = get_defaults;
sourceInfo.get_properties = get_properties;
sourceInfo.create = create;
sourceInfo.destroy = destroy;
sourceInfo.update = update;
sourceInfo.activate = activate;
sourceInfo.deactivate = deactivate;
sourceInfo.video_tick = video_tick;
sourceInfo.video_render = video_render;
obs_register_source(&sourceInfo);
}
Filter::CustomShader::~CustomShader() {}
const char * Filter::CustomShader::get_name(void *) {
return P_TRANSLATE(S);
}
void Filter::CustomShader::get_defaults(obs_data_t *data) {
obs_data_set_default_int(data, S_TYPE, (int64_t)ShaderType::Text);
obs_data_set_default_string(data, S_CONTENT_TEXT, DEFAULT_SHADER);
}
obs_properties_t * Filter::CustomShader::get_properties(void *ptr) {
obs_properties_t *pr = obs_properties_create();
obs_property_t *p;
p = obs_properties_add_list(pr, S_TYPE, P_TRANSLATE(S_TYPE), obs_combo_type::OBS_COMBO_TYPE_LIST,
obs_combo_format::OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(p, P_TRANSLATE(S_TYPE_TEXT), (int64_t)ShaderType::Text);
obs_property_list_add_int(p, P_TRANSLATE(S_TYPE_FILE), (int64_t)ShaderType::File);
obs_property_set_modified_callback(p, modified_properties);
p = obs_properties_add_text(pr, S_CONTENT_TEXT, P_TRANSLATE(S_CONTENT_TEXT),
obs_text_type::OBS_TEXT_MULTILINE);
std::string defaultPath = "C:\\";
if (ptr)
defaultPath = reinterpret_cast<Instance*>(ptr)->get_shader_file();
p = obs_properties_add_path(pr, S_CONTENT_FILE, P_TRANSLATE(S_CONTENT_FILE),
obs_path_type::OBS_PATH_FILE, "OBS Studio Effect (*.effect);;Any File (*.*)", defaultPath.c_str());
if (ptr)
reinterpret_cast<Instance*>(ptr)->get_properties(pr);
return pr;
}
bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *p, obs_data_t *data) {
ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE);
switch (shaderType) {
case ShaderType::Text:
obs_property_set_visible(obs_properties_get(pr, S_CONTENT_TEXT), true);
obs_property_set_visible(obs_properties_get(pr, S_CONTENT_FILE), false);
break;
case ShaderType::File:
obs_property_set_visible(obs_properties_get(pr, S_CONTENT_TEXT), false);
obs_property_set_visible(obs_properties_get(pr, S_CONTENT_FILE), true);
break;
}
return true;
}
void * Filter::CustomShader::create(obs_data_t *data, obs_source_t *src) {
return new Instance(data, src);
}
void Filter::CustomShader::destroy(void *ptr) {
delete reinterpret_cast<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_sourceContext = source;
update(data);
}
Filter::CustomShader::Instance::~Instance() {
}
void Filter::CustomShader::Instance::update(obs_data_t *data) {
ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE);
if (shaderType == ShaderType::Text) {
const char* shaderText = obs_data_get_string(data, S_CONTENT_TEXT);
try {
m_effect = GS::Effect(shaderText, "Text Shader");
} catch (std::runtime_error& ex) {
const char* filterName = obs_source_get_name(m_sourceContext);
P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what());
}
} else if (shaderType == ShaderType::File) {
update_shader_file(obs_data_get_string(data, S_CONTENT_FILE));
}
}
uint32_t Filter::CustomShader::Instance::get_width() {
return 0;
}
uint32_t Filter::CustomShader::Instance::get_height() {
return 0;
}
void Filter::CustomShader::Instance::activate() {
m_isActive = true;
}
void Filter::CustomShader::Instance::deactivate() {
m_isActive = false;
}
void Filter::CustomShader::Instance::video_tick(float time) {
m_shaderFile.lastCheck += time;
if (m_shaderFile.lastCheck >= 0.5) {
update_shader_file(m_shaderFile.filePath);
m_shaderFile.lastCheck = 0;
}
}
void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) {
if (!m_sourceContext || !m_isActive) {
obs_source_skip_video_filter(m_sourceContext);
return;
}
obs_source_t *parent = obs_filter_get_parent(m_sourceContext);
obs_source_t *target = obs_filter_get_target(m_sourceContext);
if (!parent || !target) {
obs_source_skip_video_filter(m_sourceContext);
return;
}
uint32_t baseW = obs_source_get_base_width(target),
baseH = obs_source_get_base_height(target);
if (!baseW || !baseH) {
obs_source_skip_video_filter(m_sourceContext);
return;
}
// Temporary
obs_source_skip_video_filter(m_sourceContext);
}
void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) {
if (m_effect.GetObject() == nullptr)
return;
m_effectParameters.clear();
for (auto p : m_effect.GetParameters()) {
std::string p_name = p.GetName();
std::string p_desc = p_name;
if (IsSpecialParameter(p_name, p.GetType()))
continue;
Parameter prm;
prm.origName = p_name;
prm.origType = p.GetType();
switch (p.GetType()) {
case GS::EffectParameter::Type::Boolean:
{
prm.names.push_back(p_name);
prm.descs.push_back(p_desc);
m_effectParameters.emplace_back(prm);
obs_properties_add_bool(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str());
break;
}
case GS::EffectParameter::Type::Float:
case GS::EffectParameter::Type::Float2:
case GS::EffectParameter::Type::Float3:
case GS::EffectParameter::Type::Float4:
{
prm.names.push_back(p_name + "0");
prm.descs.push_back(p_desc + "[0]");
if (p.GetType() >= GS::EffectParameter::Type::Float2) {
prm.names.push_back(p_name + "1");
prm.descs.push_back(p_desc + "[1]");
}
if (p.GetType() >= GS::EffectParameter::Type::Float3) {
prm.names.push_back(p_name + "2");
prm.descs.push_back(p_desc + "[2]");
}
if (p.GetType() >= GS::EffectParameter::Type::Float4) {
prm.names.push_back(p_name + "3");
prm.descs.push_back(p_desc + "[3]");
}
m_effectParameters.emplace_back(prm);
obs_properties_add_float(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Float2)
obs_properties_add_float(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Float3)
obs_properties_add_float(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Float4)
obs_properties_add_float(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON);
break;
}
case GS::EffectParameter::Type::Integer:
case GS::EffectParameter::Type::Integer2:
case GS::EffectParameter::Type::Integer3:
case GS::EffectParameter::Type::Integer4:
{
prm.names.push_back(p_name + "0");
prm.descs.push_back(p_desc + "[0]");
if (p.GetType() >= GS::EffectParameter::Type::Integer2) {
prm.names.push_back(p_name + "1");
prm.descs.push_back(p_desc + "[1]");
}
if (p.GetType() >= GS::EffectParameter::Type::Integer3) {
prm.names.push_back(p_name + "2");
prm.descs.push_back(p_desc + "[2]");
}
if (p.GetType() >= GS::EffectParameter::Type::Integer4) {
prm.names.push_back(p_name + "3");
prm.descs.push_back(p_desc + "[3]");
}
m_effectParameters.emplace_back(prm);
obs_properties_add_int(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), INT_MIN, INT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Integer2)
obs_properties_add_int(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), INT_MIN, INT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Integer3)
obs_properties_add_int(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), INT_MIN, INT_MAX, FLT_EPSILON);
if (p.GetType() >= GS::EffectParameter::Type::Integer4)
obs_properties_add_int(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), INT_MIN, INT_MAX, FLT_EPSILON);
break;
}
}
}
return;
}
void Filter::CustomShader::Instance::update_shader_file(std::string file) {
bool doRefresh = false;
struct stat stats;
if (file != m_shaderFile.filePath) {
m_shaderFile.filePath = file;
doRefresh = true;
} else if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) {
doRefresh = doRefresh ||
(m_shaderFile.createTime != stats.st_ctime) ||
(m_shaderFile.modifiedTime != stats.st_mtime);
m_shaderFile.createTime = stats.st_ctime;
m_shaderFile.modifiedTime = stats.st_mtime;
}
if (doRefresh) {
try {
m_effect = GS::Effect(m_shaderFile.filePath);
} catch (std::runtime_error& ex) {
const char* filterName = obs_source_get_name(m_sourceContext);
P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what());
}
}
}
std::string Filter::CustomShader::Instance::get_shader_file() {
return m_shaderFile.filePath;
}
bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) {
if (type == GS::EffectParameter::Type::Matrix) {
if (name == "ViewProj")
return true;
} else if (type == GS::EffectParameter::Type::Float2) {
if (name == "ViewSize")
return true;
// Suffix Tests
size_t posUnderscore = name.find_last_of('_');
if (posUnderscore != std::string::npos) {
std::string firstPart, secondPart;
firstPart = name.substr(0, posUnderscore);
secondPart = name.substr(posUnderscore + 1);
// Texture Specials
if ((secondPart == "size") || (secondPart == "texel")) {
try {
GS::EffectParameter texParam = m_effect.GetParameter(firstPart);
if (texParam.GetType() == GS::EffectParameter::Type::Texture)
return true;
} catch (...) {}
}
}
} else if (type == GS::EffectParameter::Type::Float) {
if (name == "Time" || name == "TimeActive")
return true;
}
return false;
}

View File

@ -0,0 +1,111 @@
/*
* Modern effects for a modern Streamer
* Copyright (C) 2017 Michael Fabian Dirks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include "plugin.h"
#include "gs-effect.h"
#include <list>
#include <vector>
namespace Filter {
class CustomShader {
public:
CustomShader();
~CustomShader();
static const char *get_name(void *);
static void get_defaults(obs_data_t *);
static obs_properties_t *get_properties(void *);
static bool modified_properties(obs_properties_t *,
obs_property_t *, obs_data_t *);
static void *create(obs_data_t *, obs_source_t *);
static void destroy(void *);
static uint32_t get_width(void *);
static uint32_t get_height(void *);
static void update(void *, obs_data_t *);
static void activate(void *);
static void deactivate(void *);
static void video_tick(void *, float);
static void video_render(void *, gs_effect_t *);
private:
obs_source_info sourceInfo;
private:
class Instance {
public:
Instance(obs_data_t*, obs_source_t*);
~Instance();
void update(obs_data_t*);
uint32_t get_width();
uint32_t get_height();
void activate();
void deactivate();
void video_tick(float);
void video_render(gs_effect_t*);
void get_properties(obs_properties_t *);
protected:
void update_shader_file(std::string file);
std::string get_shader_file();
bool IsSpecialParameter(std::string name, GS::EffectParameter::Type type);
void ApplySpecialParameter();
private:
obs_source_t *m_sourceContext;
bool m_isActive = true;
// Shader
struct {
std::string filePath;
time_t createTime, modifiedTime;
size_t size;
float_t lastCheck;
} m_shaderFile;
GS::Effect m_effect;
struct Parameter {
std::vector<std::string> names;
std::vector<std::string> descs;
std::string origName;
GS::EffectParameter::Type origType;
// Texture Input
bool texInputSource = false;
struct {
std::string path;
time_t timeCreated, timeModified;
size_t fileSize;
float_t lastChecked;
} textureFile;
struct {
obs_source_t* source;
std::pair<uint32_t, uint32_t> resolution;
} textureSource;
};
std::list<Parameter> m_effectParameters;
friend class CustomShader;
};
};
}