diff --git a/source/filter-custom-shader.cpp b/source/filter-custom-shader.cpp index 453e7e9..9beb150 100644 --- a/source/filter-custom-shader.cpp +++ b/source/filter-custom-shader.cpp @@ -40,19 +40,25 @@ extern "C" { #define DEFAULT_SHADER "" -/* Special Parameters - -Default: -- ViewSize - View Resolution, float2 -- ViewSizeI - View Resolution, integer2 -- Pass - Current Pass Index, integer - -Texture2d: -- $(param)_size - Texture Size, float2 -- $(param)_isize - Texture Size, integer2 -- $(param)_texel - Texture Texel Size, float2 - -*/ +/** Shader Definitions + * - Only one technique, any number of passes. + * - Technique must be named 'Draw'. + * - Parameters are split by the last underscore (_) to determine if it is a special parameter or not. + * + * Predefined Parameters: + * - ViewProj: The current view projection matrix (float4x4). + * - ViewSize: The current rendered size (float2). + * - ViewSizeI: The current rendered size as an int (int2). + * - Pass: The current pass (int). + * - Time: Time passed during total rendering in seconds (float). + * - TimeActive: Time since last activation (float). + * - image: The source being filtered (texture2d). + * + * Texture Special Parameters: + * - $(name)_Size: Texture Size (float2). + * - $(name)_SizeI: Texture Size (int2). + * - $(name)_Texel: Texture Texel Size (1/Texture Size) (float2). +**/ enum class ShaderType : int64_t { Text, @@ -101,7 +107,7 @@ void Filter::CustomShader::get_defaults(obs_data_t *data) { } obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { - obs_properties_t *pr = obs_properties_create(); + obs_properties_t *pr = obs_properties_create_param(ptr, nullptr); obs_property_t *p; p = obs_properties_add_list(pr, S_TYPE, P_TRANSLATE(S_TYPE), obs_combo_type::OBS_COMBO_TYPE_LIST, @@ -113,9 +119,9 @@ obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { p = obs_properties_add_text(pr, S_CONTENT_TEXT, P_TRANSLATE(S_CONTENT_TEXT), obs_text_type::OBS_TEXT_MULTILINE); - std::string defaultPath = "C:\\"; + std::string defaultPath = obs_module_file("effects/displace.effect"); if (ptr) - defaultPath = reinterpret_cast(ptr)->get_shader_file(); + defaultPath = reinterpret_cast(ptr)->GetShaderFile(); p = obs_properties_add_path(pr, S_CONTENT_FILE, P_TRANSLATE(S_CONTENT_FILE), obs_path_type::OBS_PATH_FILE, "OBS Studio Effect (*.effect);;Any File (*.*)", defaultPath.c_str()); @@ -125,7 +131,7 @@ obs_properties_t * Filter::CustomShader::get_properties(void *ptr) { return pr; } -bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *p, obs_data_t *data) { +bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_property_t *, obs_data_t *data) { ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); switch (shaderType) { case ShaderType::Text: @@ -138,6 +144,17 @@ bool Filter::CustomShader::modified_properties(obs_properties_t *pr, obs_propert break; } + Instance* ptr = static_cast(obs_properties_get_param(pr)); + for (Instance::Parameter& prm : ptr->m_effectParameters) { + switch (prm.type) { + case GS::EffectParameter::Type::Texture: + bool isSource = obs_data_get_int(data, prm.uiNames[0].c_str()) == 1; + obs_property_set_visible(obs_properties_get(pr, prm.uiNames[1].c_str()), isSource); + obs_property_set_visible(obs_properties_get(pr, prm.uiNames[2].c_str()), !isSource); + break; + } + } + return true; } @@ -179,14 +196,13 @@ void Filter::CustomShader::video_render(void *ptr, gs_effect_t *effect) { } Filter::CustomShader::Instance::Instance(obs_data_t *data, obs_source_t *source) { - m_sourceContext = source; - + m_source = source; + m_shaderFile.filePath = obs_module_file("effects/displace.effect"); + m_renderTarget = std::make_unique(GS_RGBA, GS_ZS_NONE); update(data); } -Filter::CustomShader::Instance::~Instance() { - -} +Filter::CustomShader::Instance::~Instance() {} void Filter::CustomShader::Instance::update(obs_data_t *data) { ShaderType shaderType = (ShaderType)obs_data_get_int(data, S_TYPE); @@ -195,11 +211,147 @@ void Filter::CustomShader::Instance::update(obs_data_t *data) { try { m_effect = GS::Effect(shaderText, "Text Shader"); } catch (std::runtime_error& ex) { - const char* filterName = obs_source_get_name(m_sourceContext); + const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); } } else if (shaderType == ShaderType::File) { - update_shader_file(obs_data_get_string(data, S_CONTENT_FILE)); + CheckShaderFile(obs_data_get_string(data, S_CONTENT_FILE), 0.0f); + } + + m_effectParameters.clear(); + if (m_effect.CountParameters() > 0) { + for (auto p : m_effect.GetParameters()) { + std::string p_name = p.GetName(); + std::string p_desc = p_name; + + if (IsSpecialParameter(p_name, p.GetType())) + continue; + + Parameter prm; + prm.name = p_name; + prm.type = p.GetType(); + + switch (p.GetType()) { + case GS::EffectParameter::Type::Boolean: + { + prm.uiNames.push_back(p_name); + prm.uiDescriptions.push_back(p_desc); + prm.value.b = obs_data_get_bool(data, p_name.c_str()); + break; + } + case GS::EffectParameter::Type::Float: + case GS::EffectParameter::Type::Float2: + case GS::EffectParameter::Type::Float3: + case GS::EffectParameter::Type::Float4: + { + { + prm.uiNames.push_back(p_name + "0"); + prm.uiDescriptions.push_back(p_desc + "[0]"); + prm.value.f[0] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float2) { + prm.uiNames.push_back(p_name + "1"); + prm.uiDescriptions.push_back(p_desc + "[1]"); + prm.value.f[1] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float3) { + prm.uiNames.push_back(p_name + "2"); + prm.uiDescriptions.push_back(p_desc + "[2]"); + prm.value.f[2] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Float4) { + prm.uiNames.push_back(p_name + "3"); + prm.uiDescriptions.push_back(p_desc + "[3]"); + prm.value.f[3] = obs_data_get_double(data, prm.uiNames.back().c_str()); + } + break; + } + case GS::EffectParameter::Type::Integer: + case GS::EffectParameter::Type::Integer2: + case GS::EffectParameter::Type::Integer3: + case GS::EffectParameter::Type::Integer4: + { + { + prm.uiNames.push_back(p_name + "0"); + prm.uiDescriptions.push_back(p_desc + "[0]"); + prm.value.i[0] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer2) { + prm.uiNames.push_back(p_name + "1"); + prm.uiDescriptions.push_back(p_desc + "[1]"); + prm.value.i[1] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer3) { + prm.uiNames.push_back(p_name + "2"); + prm.uiDescriptions.push_back(p_desc + "[2]"); + prm.value.i[2] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + if (p.GetType() >= GS::EffectParameter::Type::Integer4) { + prm.uiNames.push_back(p_name + "3"); + prm.uiDescriptions.push_back(p_desc + "[3]"); + prm.value.i[3] = obs_data_get_int(data, prm.uiNames.back().c_str()); + } + break; + } + case GS::EffectParameter::Type::Texture: + { + prm.uiNames.push_back(p_name + "_type"); + prm.uiDescriptions.push_back(p_desc + " Type"); + prm.value.textureIsSource = obs_data_get_int(data, prm.uiNames.back().c_str()) == 1; + prm.uiNames.push_back(p_name + "_source"); + prm.uiDescriptions.push_back(p_desc); + prm.value.source.name = obs_data_get_string(data, prm.uiNames.back().c_str()); + prm.uiNames.push_back(p_name + "_file"); + prm.uiDescriptions.push_back(p_desc); + if (obs_data_has_user_value(data, prm.uiNames.back().c_str())) { + prm.value.file.path = obs_data_get_string(data, prm.uiNames.back().c_str()); + } else { + prm.value.file.path = obs_module_file("white.png"); + } + break; + } + } + m_effectParameters.emplace_back(prm); + } + } + + for (Parameter& prm : m_effectParameters) { + switch (prm.type) { + case GS::EffectParameter::Type::Boolean: + prm.value.b = obs_data_get_bool(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Float4: + prm.value.f[3] = (float_t)obs_data_get_double(data, prm.uiNames[3].c_str()); + case GS::EffectParameter::Type::Float3: + prm.value.f[2] = (float_t)obs_data_get_double(data, prm.uiNames[2].c_str()); + case GS::EffectParameter::Type::Float2: + prm.value.f[1] = (float_t)obs_data_get_double(data, prm.uiNames[1].c_str()); + case GS::EffectParameter::Type::Float: + prm.value.f[0] = (float_t)obs_data_get_double(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Integer4: + prm.value.i[3] = (int32_t)obs_data_get_int(data, prm.uiNames[3].c_str()); + case GS::EffectParameter::Type::Integer3: + prm.value.i[2] = (int32_t)obs_data_get_int(data, prm.uiNames[2].c_str()); + case GS::EffectParameter::Type::Integer2: + prm.value.i[1] = (int32_t)obs_data_get_int(data, prm.uiNames[1].c_str()); + case GS::EffectParameter::Type::Integer: + prm.value.i[0] = (int32_t)obs_data_get_int(data, prm.uiNames[0].c_str()); + break; + case GS::EffectParameter::Type::Texture: + prm.value.textureIsSource = obs_data_get_int(data, prm.uiNames[0].c_str()) == 1; + std::string nName = obs_data_get_string(data, prm.uiNames[1].c_str()); + if (nName != prm.value.source.name) { + prm.value.source.name = nName; + prm.value.source.dirty = true; + } + std::string nPath = obs_data_get_string(data, prm.uiNames[2].c_str()); + if (nPath != prm.value.file.path) { + prm.value.file.path = nPath; + prm.value.file.dirty = true; + } + break; + } } } @@ -220,140 +372,196 @@ void Filter::CustomShader::Instance::deactivate() { } void Filter::CustomShader::Instance::video_tick(float time) { - m_shaderFile.lastCheck += time; - if (m_shaderFile.lastCheck >= 0.5) { - update_shader_file(m_shaderFile.filePath); - m_shaderFile.lastCheck = 0; - } + CheckShaderFile(m_shaderFile.filePath, time); + CheckTextures(time); } void Filter::CustomShader::Instance::video_render(gs_effect_t *effect) { - if (!m_sourceContext || !m_isActive) { - obs_source_skip_video_filter(m_sourceContext); + if (!m_source || !m_isActive) { + obs_source_skip_video_filter(m_source); return; } - obs_source_t *parent = obs_filter_get_parent(m_sourceContext); - obs_source_t *target = obs_filter_get_target(m_sourceContext); - if (!parent || !target) { - obs_source_skip_video_filter(m_sourceContext); + obs_source_t *parent = obs_filter_get_parent(m_source); + obs_source_t *target = obs_filter_get_target(m_source); + if (!parent || !target || !m_effect.GetObject()) { + obs_source_skip_video_filter(m_source); return; } uint32_t baseW = obs_source_get_base_width(target), baseH = obs_source_get_base_height(target); if (!baseW || !baseH) { - obs_source_skip_video_filter(m_sourceContext); + obs_source_skip_video_filter(m_source); return; } - // Temporary - obs_source_skip_video_filter(m_sourceContext); + // Render original source to texture. + { + auto op = m_renderTarget->Render(baseW, baseH); + vec4 black; vec4_zero(&black); + gs_ortho(0, (float_t)baseW, 0, (float_t)baseH, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + if (obs_source_process_filter_begin(m_source, GS_RGBA, OBS_NO_DIRECT_RENDERING)) { + obs_source_process_filter_end(m_source, + effect ? effect : obs_get_base_effect(OBS_EFFECT_DEFAULT), baseW, baseH); + } else { + obs_source_skip_video_filter(m_source); + return; + } + } + + // Apply Parameters + try { + for (Parameter& prm : m_effectParameters) { + GS::EffectParameter eprm = m_effect.GetParameter(prm.name); + switch (prm.type) { + case GS::EffectParameter::Type::Boolean: + eprm.SetBoolean(prm.value.b); + break; + case GS::EffectParameter::Type::Integer: + eprm.SetInteger(prm.value.i[0]); + break; + case GS::EffectParameter::Type::Integer2: + eprm.SetInteger2(prm.value.i[0], prm.value.i[1]); + break; + case GS::EffectParameter::Type::Integer3: + eprm.SetInteger3(prm.value.i[0], prm.value.i[1], prm.value.i[2]); + break; + case GS::EffectParameter::Type::Integer4: + eprm.SetInteger4(prm.value.i[0], prm.value.i[1], prm.value.i[2], prm.value.i[3]); + break; + case GS::EffectParameter::Type::Float: + eprm.SetFloat(prm.value.f[0]); + break; + case GS::EffectParameter::Type::Float2: + eprm.SetFloat2(prm.value.f[0], prm.value.f[1]); + break; + case GS::EffectParameter::Type::Float3: + eprm.SetFloat3(prm.value.f[0], prm.value.f[1], prm.value.f[2]); + break; + case GS::EffectParameter::Type::Float4: + eprm.SetFloat4(prm.value.f[0], prm.value.f[1], prm.value.f[2], prm.value.f[3]); + break; + case GS::EffectParameter::Type::Texture: + if (prm.value.textureIsSource) { + if (prm.value.source.rendertarget) { + uint32_t w, h; + w = obs_source_get_width(prm.value.source.source); + h = obs_source_get_height(prm.value.source.source); + { + auto op = prm.value.source.rendertarget->Render(w, h); + vec4 black; vec4_zero(&black); + gs_ortho(0, (float_t)w, 0, (float_t)h, 0, 1); + gs_clear(GS_CLEAR_COLOR, &black, 0, 0); + obs_source_video_render(prm.value.source.source); + } + eprm.SetTexture(prm.value.source.rendertarget->GetTextureObject()); + } + } else { + if (prm.value.file.texture) { + eprm.SetTexture(prm.value.file.texture); + } + } + break; + } + } + + } catch (...) { + obs_source_skip_video_filter(m_source); + return; + } + + gs_reset_blend_state(); + gs_enable_depth_test(false); + while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { + obs_source_draw(m_renderTarget->GetTextureObject(), 0, 0, baseW, baseH, false); + } } -void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { +static bool UpdateSourceListCB(void *ptr, obs_source_t* src) { + obs_property_t* p = (obs_property_t*)ptr; + obs_property_list_add_string(p, obs_source_get_name(src), obs_source_get_name(src)); + return true; +} + +static void UpdateSourceList(obs_property_t* p) { + obs_property_list_clear(p); + obs_enum_sources(UpdateSourceListCB, p); +} + +void Filter::CustomShader::Instance::get_properties(obs_properties_t *pr) { if (m_effect.GetObject() == nullptr) return; - m_effectParameters.clear(); - for (auto p : m_effect.GetParameters()) { - std::string p_name = p.GetName(); - std::string p_desc = p_name; - - if (IsSpecialParameter(p_name, p.GetType())) - continue; - - Parameter prm; - prm.origName = p_name; - prm.origType = p.GetType(); - - switch (p.GetType()) { + for (Parameter& prm : m_effectParameters) { + switch (prm.type) { case GS::EffectParameter::Type::Boolean: - { - prm.names.push_back(p_name); - prm.descs.push_back(p_desc); - m_effectParameters.emplace_back(prm); - obs_properties_add_bool(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str()); + obs_properties_add_bool(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str()); break; - } case GS::EffectParameter::Type::Float: case GS::EffectParameter::Type::Float2: case GS::EffectParameter::Type::Float3: case GS::EffectParameter::Type::Float4: - { - prm.names.push_back(p_name + "0"); - prm.descs.push_back(p_desc + "[0]"); - if (p.GetType() >= GS::EffectParameter::Type::Float2) { - prm.names.push_back(p_name + "1"); - prm.descs.push_back(p_desc + "[1]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Float3) { - prm.names.push_back(p_name + "2"); - prm.descs.push_back(p_desc + "[2]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Float4) { - prm.names.push_back(p_name + "3"); - prm.descs.push_back(p_desc + "[3]"); - } - - m_effectParameters.emplace_back(prm); - - obs_properties_add_float(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float2) - obs_properties_add_float(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float3) - obs_properties_add_float(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Float4) - obs_properties_add_float(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); - + obs_properties_add_float(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float2) + obs_properties_add_float(pr, prm.uiNames[1].c_str(), prm.uiDescriptions[1].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float3) + obs_properties_add_float(pr, prm.uiNames[2].c_str(), prm.uiDescriptions[2].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); + if (prm.type >= GS::EffectParameter::Type::Float4) + obs_properties_add_float(pr, prm.uiNames[3].c_str(), prm.uiDescriptions[3].c_str(), FLT_MIN, FLT_MAX, FLT_EPSILON); break; - } case GS::EffectParameter::Type::Integer: case GS::EffectParameter::Type::Integer2: case GS::EffectParameter::Type::Integer3: case GS::EffectParameter::Type::Integer4: - { - prm.names.push_back(p_name + "0"); - prm.descs.push_back(p_desc + "[0]"); - if (p.GetType() >= GS::EffectParameter::Type::Integer2) { - prm.names.push_back(p_name + "1"); - prm.descs.push_back(p_desc + "[1]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Integer3) { - prm.names.push_back(p_name + "2"); - prm.descs.push_back(p_desc + "[2]"); - } - if (p.GetType() >= GS::EffectParameter::Type::Integer4) { - prm.names.push_back(p_name + "3"); - prm.descs.push_back(p_desc + "[3]"); - } - - m_effectParameters.emplace_back(prm); - - obs_properties_add_int(pr, m_effectParameters.back().names[0].c_str(), m_effectParameters.back().descs[0].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer2) - obs_properties_add_int(pr, m_effectParameters.back().names[1].c_str(), m_effectParameters.back().descs[1].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer3) - obs_properties_add_int(pr, m_effectParameters.back().names[2].c_str(), m_effectParameters.back().descs[2].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - if (p.GetType() >= GS::EffectParameter::Type::Integer4) - obs_properties_add_int(pr, m_effectParameters.back().names[3].c_str(), m_effectParameters.back().descs[3].c_str(), INT_MIN, INT_MAX, FLT_EPSILON); - + obs_properties_add_int(pr, prm.uiNames[0].c_str(), prm.uiDescriptions[0].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer2) + obs_properties_add_int(pr, prm.uiNames[1].c_str(), prm.uiDescriptions[1].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer3) + obs_properties_add_int(pr, prm.uiNames[2].c_str(), prm.uiDescriptions[2].c_str(), INT_MIN, INT_MAX, 1); + if (prm.type >= GS::EffectParameter::Type::Integer4) + obs_properties_add_int(pr, prm.uiNames[3].c_str(), prm.uiDescriptions[3].c_str(), INT_MIN, INT_MAX, 1); break; - } - } + case GS::EffectParameter::Type::Texture: + obs_property * pt = obs_properties_add_list(pr, + prm.uiNames[0].c_str(), + prm.uiDescriptions[0].c_str(), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(pt, "File", 0); + obs_property_list_add_int(pt, "Source", 1); + obs_property_set_modified_callback(pt, modified_properties); + pt = obs_properties_add_list(pr, + prm.uiNames[1].c_str(), + prm.uiDescriptions[1].c_str(), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + UpdateSourceList(pt); + + obs_properties_add_path(pr, + prm.uiNames[2].c_str(), + prm.uiDescriptions[2].c_str(), + OBS_PATH_FILE, "", prm.value.file.path.c_str()); + break; + } } return; } -void Filter::CustomShader::Instance::update_shader_file(std::string file) { +void Filter::CustomShader::Instance::CheckShaderFile(std::string file, float_t time) { bool doRefresh = false; - struct stat stats; - if (file != m_shaderFile.filePath) { m_shaderFile.filePath = file; doRefresh = true; - } else if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + } + + m_shaderFile.lastCheck += time; + if (m_shaderFile.lastCheck < 0.5f && doRefresh == false) + return; + m_shaderFile.lastCheck = m_shaderFile.lastCheck - 0.5f; + + struct stat stats; + if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { doRefresh = doRefresh || (m_shaderFile.createTime != stats.st_ctime) || (m_shaderFile.modifiedTime != stats.st_mtime); @@ -365,45 +573,105 @@ void Filter::CustomShader::Instance::update_shader_file(std::string file) { try { m_effect = GS::Effect(m_shaderFile.filePath); } catch (std::runtime_error& ex) { - const char* filterName = obs_source_get_name(m_sourceContext); + const char* filterName = obs_source_get_name(m_source); P_LOG_ERROR("[%s] Shader loading failed with error(s): %s", filterName, ex.what()); } } } -std::string Filter::CustomShader::Instance::get_shader_file() { +std::string Filter::CustomShader::Instance::GetShaderFile() { return m_shaderFile.filePath; } -bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { - if (type == GS::EffectParameter::Type::Matrix) { - if (name == "ViewProj") - return true; +void Filter::CustomShader::Instance::CheckTextures(float_t time) { + for (Parameter& prm : m_effectParameters) { + if (prm.type != GS::EffectParameter::Type::Texture) + continue; - } else if (type == GS::EffectParameter::Type::Float2) { - if (name == "ViewSize") - return true; - - // Suffix Tests - size_t posUnderscore = name.find_last_of('_'); - if (posUnderscore != std::string::npos) { - std::string firstPart, secondPart; - firstPart = name.substr(0, posUnderscore); - secondPart = name.substr(posUnderscore + 1); + if (prm.value.textureIsSource) { + if (!prm.value.source.rendertarget) { + prm.value.source.rendertarget = std::make_shared(GS_RGBA, GS_ZS_NONE); + } + if (prm.value.source.dirty || !prm.value.source.source) { + if (prm.value.source.source) + obs_source_release(prm.value.source.source); + prm.value.source.source = obs_get_source_by_name(prm.value.source.name.c_str()); + } + } else { + bool doRefresh = false; + if (prm.value.file.dirty || !prm.value.file.texture) { + doRefresh = true; + } - // Texture Specials - if ((secondPart == "size") || (secondPart == "texel")) { + prm.value.file.lastCheck += time; + if (prm.value.file.lastCheck < 0.5f && doRefresh == false) + continue; + prm.value.file.lastCheck = prm.value.file.lastCheck - 0.5f; + + struct stat stats; + if (os_stat(m_shaderFile.filePath.c_str(), &stats) != 0) { + doRefresh = doRefresh || + (prm.value.file.createTime != stats.st_ctime) || + (prm.value.file.modifiedTime != stats.st_mtime) || + (prm.value.file.fileSize != stats.st_size); + prm.value.file.createTime = stats.st_ctime; + prm.value.file.modifiedTime = stats.st_mtime; + prm.value.file.fileSize = stats.st_size; + } + + if (doRefresh) { try { - GS::EffectParameter texParam = m_effect.GetParameter(firstPart); - if (texParam.GetType() == GS::EffectParameter::Type::Texture) - return true; - } catch (...) {} + prm.value.file.texture = std::make_shared(prm.value.file.path); + } catch (std::runtime_error& ex) { + const char* filterName = obs_source_get_name(m_source); + P_LOG_ERROR("[%s] Loading texture file '%s' failed with error(s): %s", + filterName, prm.value.file.path.c_str(), ex.what()); + } } } - } else if (type == GS::EffectParameter::Type::Float) { - if (name == "Time" || name == "TimeActive") + } +} + +bool Filter::CustomShader::Instance::IsSpecialParameter(std::string name, GS::EffectParameter::Type type) { + std::pair reservedParameters[] = { + { "ViewProj", GS::EffectParameter::Type::Matrix }, + { "ViewSize", GS::EffectParameter::Type::Matrix }, + { "ViewSizeI", GS::EffectParameter::Type::Integer2 }, + { "Pass", GS::EffectParameter::Type::Integer }, + { "Time", GS::EffectParameter::Type::Float }, + { "TimeActive", GS::EffectParameter::Type::Float }, + { "image", GS::EffectParameter::Type::Texture } + }; + std::pair textureParameters[] = { + { "Size", GS::EffectParameter::Type::Float2 }, + { "SizeI", GS::EffectParameter::Type::Integer2 }, + { "Texel", GS::EffectParameter::Type::Float2 } + }; + + for (auto& kv : reservedParameters) { + if ((name == kv.first) && (type == kv.second)) return true; } + // Split by last underscore(_) (if there is one). + size_t posUnderscore = name.find_last_of('_'); + if (posUnderscore != std::string::npos) { + std::string firstPart, secondPart; + firstPart = name.substr(0, posUnderscore); + secondPart = name.substr(posUnderscore + 1); + + try { + GS::EffectParameter prm = m_effect.GetParameter(firstPart); + if (prm.GetType() == GS::EffectParameter::Type::Texture) { + for (auto& kv : reservedParameters) { + if ((secondPart == kv.first) && (type == kv.second)) + return true; + } + } + } catch (...) { + return false; + } + } + return false; } diff --git a/source/filter-custom-shader.h b/source/filter-custom-shader.h index b199a1f..85e751e 100644 --- a/source/filter-custom-shader.h +++ b/source/filter-custom-shader.h @@ -20,8 +20,10 @@ #pragma once #include "plugin.h" #include "gs-effect.h" +#include "gs-rendertarget.h" #include #include +#include namespace Filter { class CustomShader { @@ -53,7 +55,7 @@ namespace Filter { public: Instance(obs_data_t*, obs_source_t*); ~Instance(); - + void update(obs_data_t*); uint32_t get_width(); uint32_t get_height(); @@ -63,15 +65,15 @@ namespace Filter { void video_render(gs_effect_t*); void get_properties(obs_properties_t *); - protected: - void update_shader_file(std::string file); - std::string get_shader_file(); + private: + void CheckShaderFile(std::string file, float_t time); + std::string GetShaderFile(); + void CheckTextures(float_t time); bool IsSpecialParameter(std::string name, GS::EffectParameter::Type type); - void ApplySpecialParameter(); - + private: - obs_source_t *m_sourceContext; + obs_source_t * m_source; bool m_isActive = true; // Shader @@ -81,29 +83,42 @@ namespace Filter { size_t size; float_t lastCheck; } m_shaderFile; + GS::Effect m_effect; struct Parameter { - std::vector names; - std::vector descs; - std::string origName; - GS::EffectParameter::Type origType; + std::string name; + GS::EffectParameter::Type type; - // Texture Input - bool texInputSource = false; - struct { - std::string path; - time_t timeCreated, timeModified; - size_t fileSize; - float_t lastChecked; - } textureFile; - struct { - obs_source_t* source; - std::pair resolution; - } textureSource; + std::vector uiNames; + std::vector uiDescriptions; + struct { + union { + int32_t i[4]; + float_t f[4]; + bool b; + }; + bool textureIsSource = false; + struct { + bool dirty = false; + std::string name; + obs_source_t* source = nullptr; + std::shared_ptr rendertarget; + } source; + struct { + bool dirty = false; + std::string path; + time_t createTime, modifiedTime; + size_t fileSize; + float_t lastCheck; + std::shared_ptr texture; + } file; + } value; }; std::list m_effectParameters; + std::unique_ptr m_renderTarget; + friend class CustomShader; };