mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-11-10 22:05:06 +00:00
filter-custom-shader: Basic custom shader support
The Custom Shader Filter is now capable of rendering a custom shader using the gfx::effect_source class as a backend. An example shader is provided for starting off, more advanced examples may come later. Related: #5
This commit is contained in:
parent
0957ce0bb9
commit
e8da137ab6
3 changed files with 93 additions and 191 deletions
|
@ -1,19 +1,20 @@
|
|||
// Input Parameters (Auto)
|
||||
uniform float4x4 ViewProj;
|
||||
uniform float2 ViewSize;
|
||||
//uniform int2 ViewSizeI;
|
||||
uniform float Time;
|
||||
uniform float TimeActive;
|
||||
// Basic Input Parameters
|
||||
uniform float4x4 ViewProj; // View Projection Matrix
|
||||
uniform float2 ViewSize; // View Size as Float2
|
||||
//uniform int2 ViewSizeI; // View Size as Int2
|
||||
//uniform float Time; // Time Existing
|
||||
uniform float TimeActive; // Time Active
|
||||
|
||||
// Default Texture Parameter
|
||||
uniform texture2d image;
|
||||
uniform float2 image_Size;
|
||||
//uniform int2 image_SizeI;
|
||||
//uniform float2 image_Texel;
|
||||
// Filter Input Parameters
|
||||
uniform texture2d Image; // Input Image
|
||||
uniform float2 Image_Size;
|
||||
//uniform int2 Image_SizeI;
|
||||
//uniform float2 Image_Texel;
|
||||
|
||||
// Custom Parameters (these show up in the UI)
|
||||
uniform float4 colr;
|
||||
uniform float waveScale;
|
||||
// Custom Parameters (These will be visible in the UI)
|
||||
//uniform float4 Color;
|
||||
uniform float WaveScale;
|
||||
uniform float WaveAmplitude;
|
||||
|
||||
sampler_state textureSampler {
|
||||
AddressU = Wrap;
|
||||
|
@ -39,28 +40,30 @@ FragData VSDefault(VertData v_in) {
|
|||
return vert_out;
|
||||
}
|
||||
|
||||
float4 PSDefault(FragData v_in) : TARGET {
|
||||
float4 smp = image.Sample(textureSampler, v_in.uv).rgba;
|
||||
return lerp(colr, smp, sin(fmod(TimeActive / 3.0, 1.0) * 3.14));
|
||||
// Simple texture sample, technically does nothing.
|
||||
float4 PS_Sample(FragData v_in) : TARGET {
|
||||
return Image.Sample(textureSampler, v_in.uv);
|
||||
}
|
||||
|
||||
float4 PSRotate(FragData v_in) : TARGET {
|
||||
// Adding a little wave to the image makes a huge difference already.
|
||||
float4 PS_Wave(FragData v_in) : TARGET {
|
||||
float2 realPos = v_in.uv * ViewSize;
|
||||
float yoff = sin(realPos.x / WaveAmplitude + TimeActive) * ((WaveScale / 100.0) * Image_Size.y);
|
||||
return Image.Sample(textureSampler, v_in.uv + float2(0, yoff));
|
||||
}
|
||||
|
||||
// Rotating is also rather easy to do.
|
||||
float4 PS_Rotate(FragData v_in) : TARGET {
|
||||
float angle = TimeActive * 3.141 / 16.0;
|
||||
|
||||
float cp = cos(angle);
|
||||
float sp = sin(angle);
|
||||
float sn = -sp;
|
||||
float2 uv = float2((v_in.uv.x * cp) + (v_in.uv.y * sn), (v_in.uv.x * sp) + (v_in.uv.y * cp));
|
||||
float2 uv = v_in.uv * Image_Size;
|
||||
uv = float2((uv.x * cp) + (uv.y * sn), (uv.x * sp) + (uv.y * cp));
|
||||
uv /= Image_Size;
|
||||
|
||||
return image.Sample(textureSampler, uv);
|
||||
}
|
||||
|
||||
float4 PSWave(FragData v_in) : TARGET {
|
||||
float2 realPos = v_in.uv * ViewSize;
|
||||
|
||||
float yoff = sin(realPos.x / 36.0 + TimeActive) * ((waveScale / 100.0) * image_Size.y);
|
||||
|
||||
return image.Sample(textureSampler, v_in.uv + float2(0, yoff));
|
||||
return Image.Sample(textureSampler, uv);
|
||||
}
|
||||
|
||||
technique Draw
|
||||
|
@ -68,6 +71,6 @@ technique Draw
|
|||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSWave(v_in);
|
||||
pixel_shader = PS_Rotate(v_in);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,8 +141,8 @@ void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) {
|
|||
}
|
||||
|
||||
Filter::CustomShader::Instance::Instance(obs_data_t *data, obs_source_t *source) : gfx::effect_source(data, source) {
|
||||
default_shader_path = "shaders/filter/example.effect";
|
||||
|
||||
m_defaultShaderPath = "shaders/filter/example.effect";
|
||||
m_renderTarget = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
||||
update(data);
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,7 @@ bool Filter::CustomShader::Instance::is_special_parameter(std::string name, gs::
|
|||
{ "ViewSizeI", gs::effect_parameter::type::Integer2 },
|
||||
{ "Time", gs::effect_parameter::type::Float },
|
||||
{ "TimeActive", gs::effect_parameter::type::Float },
|
||||
{ "image", gs::effect_parameter::type::Texture }
|
||||
{ "Image", gs::effect_parameter::type::Texture },
|
||||
};
|
||||
std::pair<std::string, gs::effect_parameter::type> textureParameters[] = {
|
||||
{ "Size", gs::effect_parameter::type::Float2 },
|
||||
|
@ -335,7 +335,7 @@ bool Filter::CustomShader::Instance::is_special_parameter(std::string name, gs::
|
|||
secondPart = name.substr(posUnderscore + 1);
|
||||
|
||||
try {
|
||||
gs::effect_parameter prm = shader.effect->get_parameter(firstPart);
|
||||
gs::effect_parameter prm = m_shader.effect->get_parameter(firstPart);
|
||||
if (prm.get_type() == gs::effect_parameter::type::Texture) {
|
||||
for (auto& kv : textureParameters) {
|
||||
if ((secondPart == kv.first) && (type == kv.second))
|
||||
|
@ -350,104 +350,66 @@ bool Filter::CustomShader::Instance::is_special_parameter(std::string name, gs::
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Filter::CustomShader::Instance::apply_special_parameters() {
|
||||
throw std::logic_error("The method or operation is not implemented.");
|
||||
bool Filter::CustomShader::Instance::apply_special_parameters(uint32_t viewW, uint32_t viewH) {
|
||||
std::unique_ptr<gs::texture> imageTexture;
|
||||
m_renderTarget->get_texture(imageTexture);
|
||||
|
||||
if (m_shader.effect->has_parameter("Image", gs::effect_parameter::type::Texture)) {
|
||||
m_shader.effect->get_parameter("Image").set_texture(imageTexture->get_object());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (m_shader.effect->has_parameter("Image_Size", gs::effect_parameter::type::Float2)) {
|
||||
m_shader.effect->get_parameter("Image_Size").set_float2(
|
||||
float_t(imageTexture->get_width()),
|
||||
float_t(imageTexture->get_height()));
|
||||
}
|
||||
if (m_shader.effect->has_parameter("Image_SizeI"/*, gs::effect_parameter::type::Integer2*/)) {
|
||||
m_shader.effect->get_parameter("Image_SizeI").set_int2(
|
||||
imageTexture->get_width(),
|
||||
imageTexture->get_height());
|
||||
}
|
||||
if (m_shader.effect->has_parameter("Image_Texel", gs::effect_parameter::type::Float2)) {
|
||||
m_shader.effect->get_parameter("Image_Texel").set_float2(
|
||||
float_t(1.0 / imageTexture->get_width()),
|
||||
float_t(1.0 / imageTexture->get_height()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Filter::CustomShader::Instance::video_tick_impl(float time) {
|
||||
throw std::logic_error("The method or operation is not implemented.");
|
||||
bool Filter::CustomShader::Instance::video_tick_impl(float_t time) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Filter::CustomShader::Instance::video_render_impl(gs_effect_t* parent_effect) {
|
||||
throw std::logic_error("The method or operation is not implemented.");
|
||||
bool Filter::CustomShader::Instance::video_render_impl(gs_effect_t* parent_effect, uint32_t viewW, uint32_t viewH) {
|
||||
// Render original source to render target.
|
||||
{
|
||||
auto op = m_renderTarget->render(viewW, viewH);
|
||||
vec4 black; vec4_zero(&black);
|
||||
gs_ortho(0, (float_t)viewW, 0, (float_t)viewH, 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,
|
||||
parent_effect ? parent_effect : obs_get_base_effect(OBS_EFFECT_DEFAULT), viewW, viewH);
|
||||
}
|
||||
}
|
||||
gs_texture_t* sourceTexture = m_renderTarget->get_object();
|
||||
if (!sourceTexture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!apply_special_parameters(viewW, viewH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//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->get_object();
|
||||
// if (!sourceTexture) {
|
||||
// obs_source_skip_video_filter(m_source);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Apply Parameters
|
||||
// try {
|
||||
// if (m_effect.effect->has_parameter("ViewSize", gs::effect_parameter::type::Float2))
|
||||
// m_effect.effect->get_parameter("ViewSize").set_float2(float_t(baseW), float_t(baseH));
|
||||
// if (m_effect.effect->has_parameter("ViewSizeI", gs::effect_parameter::type::Integer2))
|
||||
// m_effect.effect->get_parameter("ViewSizeI").set_int2(baseW, baseH);
|
||||
// if (m_effect.effect->has_parameter("Time", gs::effect_parameter::type::Float))
|
||||
// m_effect.effect->get_parameter("Time").set_float(m_renderTime);
|
||||
// if (m_effect.effect->has_parameter("TimeActive", gs::effect_parameter::type::Float))
|
||||
// m_effect.effect->get_parameter("TimeActive").set_float(m_activeTime);
|
||||
//
|
||||
// /// "image" Specials
|
||||
// if (m_effect.effect->has_parameter("image_Size", gs::effect_parameter::type::Float2))
|
||||
// m_effect.effect->get_parameter("image_Size").set_float2(float_t(baseW), float_t(baseH));
|
||||
// if (m_effect.effect->has_parameter("image_SizeI", gs::effect_parameter::type::Float2))
|
||||
// m_effect.effect->get_parameter("image_SizeI").set_int2(baseW, baseH);
|
||||
// if (m_effect.effect->has_parameter("image_Texel", gs::effect_parameter::type::Float2))
|
||||
// m_effect.effect->get_parameter("image_Texel").set_float2(1.0f / float_t(baseW), 1.0f / float_t(baseH));
|
||||
//
|
||||
// for (Parameter& prm : m_effectParameters) {
|
||||
// gs::effect_parameter eprm = m_effect.effect->get_parameter(prm.name);
|
||||
// switch (prm.type) {
|
||||
// case gs::effect_parameter::type::Boolean:
|
||||
// eprm.set_bool(prm.value.b);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Integer:
|
||||
// eprm.set_int(prm.value.i[0]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Integer2:
|
||||
// eprm.set_int2(prm.value.i[0], prm.value.i[1]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Integer3:
|
||||
// eprm.set_int3(prm.value.i[0], prm.value.i[1], prm.value.i[2]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Integer4:
|
||||
// eprm.set_int4(prm.value.i[0], prm.value.i[1], prm.value.i[2], prm.value.i[3]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Float:
|
||||
// eprm.set_float(prm.value.f[0]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Float2:
|
||||
// eprm.set_float2(prm.value.f[0], prm.value.f[1]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Float3:
|
||||
// eprm.set_float3(prm.value.f[0], prm.value.f[1], prm.value.f[2]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Float4:
|
||||
// eprm.set_float4(prm.value.f[0], prm.value.f[1], prm.value.f[2], prm.value.f[3]);
|
||||
// break;
|
||||
// case gs::effect_parameter::type::Texture:
|
||||
// if (prm.value.textureIsSource) {
|
||||
// if (prm.value.source.rendertarget && prm.value.source.source) {
|
||||
|
@ -472,62 +434,6 @@ void Filter::CustomShader::Instance::video_render_impl(gs_effect_t* parent_effec
|
|||
// }
|
||||
// }
|
||||
//
|
||||
// } 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->get_object(), "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::effect_parameter::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;
|
||||
//}
|
||||
|
||||
//std::string Filter::CustomShader::Instance::GetShaderFile() {
|
||||
// return m_effect.path;
|
||||
//}
|
||||
|
||||
//void Filter::CustomShader::Instance::CheckTextures(float_t time) {
|
||||
|
|
|
@ -53,7 +53,13 @@ namespace Filter {
|
|||
class Instance : public gfx::effect_source {
|
||||
friend class CustomShader;
|
||||
|
||||
std::shared_ptr<gs::rendertarget> m_renderTarget;
|
||||
|
||||
protected:
|
||||
bool apply_special_parameters(uint32_t viewW, uint32_t viewH);
|
||||
virtual bool is_special_parameter(std::string name, gs::effect_parameter::type type) override;
|
||||
virtual bool video_tick_impl(float_t time) override;
|
||||
virtual bool video_render_impl(gs_effect_t* parent_effect, uint32_t viewW, uint32_t viewH) override;
|
||||
|
||||
public:
|
||||
Instance(obs_data_t*, obs_source_t*);
|
||||
|
@ -61,19 +67,6 @@ namespace Filter {
|
|||
|
||||
uint32_t get_width();
|
||||
uint32_t get_height();
|
||||
|
||||
protected:
|
||||
virtual bool is_special_parameter(std::string name, gs::effect_parameter::type type) override;
|
||||
|
||||
|
||||
virtual bool apply_special_parameters() override;
|
||||
|
||||
|
||||
virtual void video_tick_impl(float time) override;
|
||||
|
||||
|
||||
virtual void video_render_impl(gs_effect_t* parent_effect) override;
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue