/* * 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-displacement.hpp" #include #include "strings.hpp" // Initializer & Finalizer static filter::Displacement* filterDisplacementInstance; INITIALIZER(FilterDisplacementInit) { initializerFunctions.push_back([] { filterDisplacementInstance = new filter::Displacement(); }); finalizerFunctions.push_back([] { delete filterDisplacementInstance; }); } filter::Displacement::Displacement() { memset(&sourceInfo, 0, sizeof(obs_source_info)); sourceInfo.id = "obs-stream-effects-filter-displacement"; 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.show = show; sourceInfo.hide = hide; sourceInfo.video_tick = video_tick; sourceInfo.video_render = video_render; obs_register_source(&sourceInfo); } filter::Displacement::~Displacement() {} const char* filter::Displacement::get_name(void*) { return P_TRANSLATE(S_FILTER_DISPLACEMENT); } void* filter::Displacement::create(obs_data_t* data, obs_source_t* source) { return new Instance(data, source); } void filter::Displacement::destroy(void* ptr) { delete reinterpret_cast(ptr); } uint32_t filter::Displacement::get_width(void* ptr) { return reinterpret_cast(ptr)->get_width(); } uint32_t filter::Displacement::get_height(void* ptr) { return reinterpret_cast(ptr)->get_height(); } void filter::Displacement::get_defaults(obs_data_t* data) { char* disp = obs_module_file("filter-displacement/neutral.png"); obs_data_set_default_string(data, S_FILTER_DISPLACEMENT_FILE, disp); obs_data_set_default_double(data, S_FILTER_DISPLACEMENT_RATIO, 0); obs_data_set_default_double(data, S_FILTER_DISPLACEMENT_SCALE, 0); bfree(disp); } obs_properties_t* filter::Displacement::get_properties(void* ptr) { obs_properties_t* pr = obs_properties_create(); std::string path = ""; if (ptr) path = reinterpret_cast(ptr)->get_file(); obs_properties_add_path(pr, S_FILTER_DISPLACEMENT_FILE, P_TRANSLATE(S_FILTER_DISPLACEMENT_FILE), obs_path_type::OBS_PATH_FILE, P_TRANSLATE(S_FILTER_DISPLACEMENT_FILE_TYPES), path.c_str()); obs_properties_add_float_slider(pr, S_FILTER_DISPLACEMENT_RATIO, P_TRANSLATE(S_FILTER_DISPLACEMENT_RATIO), 0, 1, 0.01); obs_properties_add_float_slider(pr, S_FILTER_DISPLACEMENT_SCALE, P_TRANSLATE(S_FILTER_DISPLACEMENT_SCALE), -1000, 1000, 0.01); return pr; } void filter::Displacement::update(void* ptr, obs_data_t* data) { reinterpret_cast(ptr)->update(data); } void filter::Displacement::activate(void* ptr) { reinterpret_cast(ptr)->activate(); } void filter::Displacement::deactivate(void* ptr) { reinterpret_cast(ptr)->deactivate(); } void filter::Displacement::show(void* ptr) { reinterpret_cast(ptr)->show(); } void filter::Displacement::hide(void* ptr) { reinterpret_cast(ptr)->hide(); } void filter::Displacement::video_tick(void* ptr, float time) { reinterpret_cast(ptr)->video_tick(time); } void filter::Displacement::video_render(void* ptr, gs_effect_t* effect) { reinterpret_cast(ptr)->video_render(effect); } filter::Displacement::Instance::Instance(obs_data_t* data, obs_source_t* context) { this->dispmap.texture = nullptr; this->dispmap.createTime = 0; this->dispmap.modifiedTime = 0; this->dispmap.size = 0; this->timer = 0; this->context = context; obs_enter_graphics(); char* effectFile = obs_module_file("effects/displace.effect"); char* errorMessage = nullptr; this->customEffect = gs_effect_create_from_file(effectFile, &errorMessage); bfree(effectFile); if (errorMessage != nullptr) { P_LOG_ERROR("%s", errorMessage); bfree(errorMessage); } obs_leave_graphics(); update(data); } filter::Displacement::Instance::~Instance() { obs_enter_graphics(); gs_effect_destroy(customEffect); gs_texture_destroy(dispmap.texture); obs_leave_graphics(); } void filter::Displacement::Instance::update(obs_data_t* data) { updateDisplacementMap(obs_data_get_string(data, S_FILTER_DISPLACEMENT_FILE)); distance = float_t(obs_data_get_double(data, S_FILTER_DISPLACEMENT_RATIO)); vec2_set(&displacementScale, float_t(obs_data_get_double(data, S_FILTER_DISPLACEMENT_SCALE)), float_t(obs_data_get_double(data, S_FILTER_DISPLACEMENT_SCALE))); } uint32_t filter::Displacement::Instance::get_width() { return 0; } uint32_t filter::Displacement::Instance::get_height() { return 0; } void filter::Displacement::Instance::activate() {} void filter::Displacement::Instance::deactivate() {} void filter::Displacement::Instance::show() {} void filter::Displacement::Instance::hide() {} void filter::Displacement::Instance::video_tick(float time) { timer += time; if (timer >= 1.0) { timer -= 1.0; updateDisplacementMap(dispmap.file); } } float interp(float a, float b, float v) { return (a * (1.0f - v)) + (b * v); } void filter::Displacement::Instance::video_render(gs_effect_t*) { obs_source_t* parent = obs_filter_get_parent(context); obs_source_t* target = obs_filter_get_target(context); uint32_t baseW = obs_source_get_base_width(target), baseH = obs_source_get_base_height(target); // Skip rendering if our target, parent or context is not valid. if (!target || !parent || !context || !dispmap.texture || !baseW || !baseH) { obs_source_skip_video_filter(context); return; } if (!obs_source_process_filter_begin(context, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) return; gs_eparam_t* param; vec2 texelScale; vec2_set(&texelScale, interp((1.0f / baseW), 1.0f, distance), interp((1.0f / baseH), 1.0f, distance)); param = gs_effect_get_param_by_name(customEffect, "texelScale"); if (param) gs_effect_set_vec2(param, &texelScale); else P_LOG_ERROR("Failed to set texel scale param."); param = gs_effect_get_param_by_name(customEffect, "displacementScale"); if (param) gs_effect_set_vec2(param, &displacementScale); else P_LOG_ERROR("Failed to set displacement scale param."); param = gs_effect_get_param_by_name(customEffect, "displacementMap"); if (param) gs_effect_set_texture(param, dispmap.texture); else P_LOG_ERROR("Failed to set texture param."); obs_source_process_filter_end(context, customEffect, baseW, baseH); } std::string filter::Displacement::Instance::get_file() { return dispmap.file; } void filter::Displacement::Instance::updateDisplacementMap(std::string file) { bool shouldUpdateTexture = false; // Different File if (file != dispmap.file) { dispmap.file = file; shouldUpdateTexture = true; } else { // Different Timestamps struct stat stats; if (os_stat(dispmap.file.c_str(), &stats) != 0) { shouldUpdateTexture = shouldUpdateTexture || (dispmap.createTime != stats.st_ctime) || (dispmap.modifiedTime != stats.st_mtime); dispmap.createTime = stats.st_ctime; dispmap.modifiedTime = stats.st_mtime; } } if (shouldUpdateTexture) { obs_enter_graphics(); if (dispmap.texture) { gs_texture_destroy(dispmap.texture); dispmap.texture = nullptr; } if (os_file_exists(file.c_str())) dispmap.texture = gs_texture_create_from_file(dispmap.file.c_str()); obs_leave_graphics(); } }