mirror of https://github.com/Xaymar/obs-StreamFX
837 lines
32 KiB
C++
837 lines
32 KiB
C++
// AUTOGENERATED COPYRIGHT HEADER START
|
|
// Copyright (C) 2017-2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
|
|
// Copyright (C) 2022 lainon <GermanAizek@yandex.ru>
|
|
// AUTOGENERATED COPYRIGHT HEADER END
|
|
|
|
#include "filter-transform.hpp"
|
|
#include "strings.hpp"
|
|
#include "obs/gs/gs-helper.hpp"
|
|
#include "util/util-logging.hpp"
|
|
|
|
#include "warning-disable.hpp"
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
#include "warning-enable.hpp"
|
|
|
|
// OBS
|
|
#include "warning-disable.hpp"
|
|
#include <graphics/graphics.h>
|
|
#include <graphics/matrix4.h>
|
|
#include <util/platform.h>
|
|
#include "warning-enable.hpp"
|
|
|
|
#ifdef _DEBUG
|
|
#define ST_PREFIX "<%s> "
|
|
#define D_LOG_ERROR(x, ...) P_LOG_ERROR(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
|
|
#define D_LOG_WARNING(x, ...) P_LOG_WARN(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
|
|
#define D_LOG_INFO(x, ...) P_LOG_INFO(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
|
|
#define D_LOG_DEBUG(x, ...) P_LOG_DEBUG(ST_PREFIX##x, __FUNCTION_SIG__, __VA_ARGS__)
|
|
#else
|
|
#define ST_PREFIX "<filter::transform> "
|
|
#define D_LOG_ERROR(...) P_LOG_ERROR(ST_PREFIX __VA_ARGS__)
|
|
#define D_LOG_WARNING(...) P_LOG_WARN(ST_PREFIX __VA_ARGS__)
|
|
#define D_LOG_INFO(...) P_LOG_INFO(ST_PREFIX __VA_ARGS__)
|
|
#define D_LOG_DEBUG(...) P_LOG_DEBUG(ST_PREFIX __VA_ARGS__)
|
|
#endif
|
|
|
|
#define ST_I18N "Filter.Transform"
|
|
#define ST_I18N_CAMERA ST_I18N ".Camera"
|
|
#define ST_I18N_CAMERA_MODE ST_I18N_CAMERA ".Mode"
|
|
#define ST_KEY_CAMERA_MODE "Camera.Mode"
|
|
#define ST_I18N_CAMERA_MODE_CORNER_PIN ST_I18N_CAMERA_MODE ".CornerPin"
|
|
#define ST_I18N_CAMERA_MODE_ORTHOGRAPHIC ST_I18N_CAMERA_MODE ".Orthographic"
|
|
#define ST_I18N_CAMERA_MODE_PERSPECTIVE ST_I18N_CAMERA_MODE ".Perspective"
|
|
#define ST_I18N_CAMERA_FIELDOFVIEW ST_I18N_CAMERA ".FieldOfView"
|
|
#define ST_KEY_CAMERA_FIELDOFVIEW "Camera.FieldOfView"
|
|
#define ST_I18N_POSITION ST_I18N ".Position"
|
|
#define ST_KEY_POSITION_X "Position.X"
|
|
#define ST_KEY_POSITION_Y "Position.Y"
|
|
#define ST_KEY_POSITION_Z "Position.Z"
|
|
#define ST_I18N_ROTATION ST_I18N ".Rotation"
|
|
#define ST_KEY_ROTATION_X "Rotation.X"
|
|
#define ST_KEY_ROTATION_Y "Rotation.Y"
|
|
#define ST_KEY_ROTATION_Z "Rotation.Z"
|
|
#define ST_I18N_SCALE ST_I18N ".Scale"
|
|
#define ST_KEY_SCALE_X "Scale.X"
|
|
#define ST_KEY_SCALE_Y "Scale.Y"
|
|
#define ST_I18N_SHEAR ST_I18N ".Shear"
|
|
#define ST_KEY_SHEAR_X "Shear.X"
|
|
#define ST_KEY_SHEAR_Y "Shear.Y"
|
|
#define ST_I18N_ROTATION_ORDER ST_I18N ".Rotation.Order"
|
|
#define ST_KEY_ROTATION_ORDER "Rotation.Order"
|
|
#define ST_I18N_ROTATION_ORDER_XYZ ST_I18N_ROTATION_ORDER ".XYZ"
|
|
#define ST_I18N_ROTATION_ORDER_XZY ST_I18N_ROTATION_ORDER ".XZY"
|
|
#define ST_I18N_ROTATION_ORDER_YXZ ST_I18N_ROTATION_ORDER ".YXZ"
|
|
#define ST_I18N_ROTATION_ORDER_YZX ST_I18N_ROTATION_ORDER ".YZX"
|
|
#define ST_I18N_ROTATION_ORDER_ZXY ST_I18N_ROTATION_ORDER ".ZXY"
|
|
#define ST_I18N_ROTATION_ORDER_ZYX ST_I18N_ROTATION_ORDER ".ZYX"
|
|
#define ST_I18N_CORNERS ST_I18N ".Corners"
|
|
#define ST_I18N_CORNERS_TOPLEFT ST_I18N_CORNERS ".TopLeft"
|
|
#define ST_KEY_CORNERS_TOPLEFT "Corners.TopLeft."
|
|
#define ST_I18N_CORNERS_TOPRIGHT ST_I18N_CORNERS ".TopRight"
|
|
#define ST_KEY_CORNERS_TOPRIGHT "Corners.TopRight."
|
|
#define ST_I18N_CORNERS_BOTTOMLEFT ST_I18N_CORNERS ".BottomLeft"
|
|
#define ST_KEY_CORNERS_BOTTOMLEFT "Corners.BottomLeft."
|
|
#define ST_I18N_CORNERS_BOTTOMRIGHT ST_I18N_CORNERS ".BottomRight"
|
|
#define ST_KEY_CORNERS_BOTTOMRIGHT "Corners.BottomRight."
|
|
#define ST_I18N_MIPMAPPING ST_I18N ".Mipmapping"
|
|
#define ST_KEY_MIPMAPPING "Mipmapping"
|
|
|
|
using namespace streamfx::filter::transform;
|
|
|
|
static constexpr std::string_view HELP_URL = "https://github.com/Xaymar/obs-StreamFX/wiki/Filter-3D-Transform";
|
|
|
|
static const float farZ = 2097152.0f; // 2 pow 21
|
|
static const float nearZ = 1.0f / farZ;
|
|
|
|
enum RotationOrder : int64_t {
|
|
XYZ = 0,
|
|
XZY = 1,
|
|
YXZ = 2,
|
|
YZX = 3,
|
|
ZXY = 4,
|
|
ZYX = 5,
|
|
};
|
|
|
|
transform_instance::transform_instance(obs_data_t* data, obs_source_t* context) : obs::source_instance(data, context), _gfx_util(::streamfx::gfx::util::get()), _camera_mode(), _camera_fov(), _params(), _corners(), _standard_effect(), _transform_effect(), _sampler(), _cache_rendered(), _mipmap_enabled(), _source_rendered(), _source_size(), _update_mesh(true)
|
|
{
|
|
{
|
|
auto gctx = obs::gs::context();
|
|
|
|
_cache_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
|
_source_rt = std::make_shared<streamfx::obs::gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
|
_vertex_buffer = std::make_shared<streamfx::obs::gs::vertex_buffer>(uint32_t(4u), uint8_t(1u));
|
|
{
|
|
auto file = streamfx::data_file_path("effects/standard.effect");
|
|
try {
|
|
_standard_effect = streamfx::obs::gs::effect::create(file);
|
|
} catch (const std::exception& ex) {
|
|
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
|
|
}
|
|
}
|
|
{
|
|
auto file = streamfx::data_file_path("effects/transform.effect");
|
|
try {
|
|
_transform_effect = streamfx::obs::gs::effect::create(file);
|
|
} catch (const std::exception& ex) {
|
|
DLOG_ERROR("Error loading '%s': %s", file.generic_u8string().c_str(), ex.what());
|
|
}
|
|
}
|
|
{
|
|
_sampler.set_address_mode_u(GS_ADDRESS_CLAMP);
|
|
_sampler.set_address_mode_v(GS_ADDRESS_CLAMP);
|
|
_sampler.set_address_mode_w(GS_ADDRESS_CLAMP);
|
|
_sampler.set_filter(GS_FILTER_LINEAR);
|
|
_sampler.set_max_anisotropy(8);
|
|
}
|
|
|
|
vec3_set(&_params.position, 0, 0, 0);
|
|
vec3_set(&_params.rotation, 0, 0, 0);
|
|
vec3_set(&_params.scale, 1, 1, 1);
|
|
vec3_set(&_params.shear, 0, 0, 0);
|
|
|
|
vec2_set(&_corners.tl, 0, 0);
|
|
vec2_set(&_corners.tr, 1, 0);
|
|
vec2_set(&_corners.bl, 0, 1);
|
|
vec2_set(&_corners.br, 1, 1);
|
|
}
|
|
|
|
update(data);
|
|
}
|
|
|
|
transform_instance::~transform_instance()
|
|
{
|
|
_vertex_buffer.reset();
|
|
_cache_rt.reset();
|
|
_cache_texture.reset();
|
|
_mipmap_texture.reset();
|
|
}
|
|
|
|
void transform_instance::load(obs_data_t* settings)
|
|
{
|
|
update(settings);
|
|
}
|
|
|
|
void transform_instance::migrate(obs_data_t* settings, uint64_t version)
|
|
{
|
|
// Only test for A.B.C in A.B.C.D
|
|
version = version & STREAMFX_MASK_UPDATE;
|
|
|
|
#define COPY_UNSET(TYPE, NAME, OLDNAME) \
|
|
if (obs_data_has_user_value(settings, OLDNAME)) { \
|
|
obs_data_set_##TYPE(settings, NAME, obs_data_get_##TYPE(settings, OLDNAME)); \
|
|
obs_data_unset_user_value(settings, OLDNAME); \
|
|
}
|
|
#define COPY_IGNORE(TYPE, NAME, OLDNAME) \
|
|
if (obs_data_has_user_value(settings, OLDNAME)) { \
|
|
obs_data_set_##TYPE(settings, NAME, obs_data_get_##TYPE(settings, OLDNAME)); \
|
|
}
|
|
#define SET_IF_UNSET(TYPE, NAME, value) \
|
|
if (!obs_data_has_user_value(settings, NAME)) { \
|
|
obs_data_set_##TYPE(settings, NAME, value); \
|
|
}
|
|
|
|
if (version < STREAMFX_MAKE_VERSION(0, 8, 0, 0)) {
|
|
COPY_IGNORE(double, ST_KEY_ROTATION_X, ST_KEY_ROTATION_X);
|
|
COPY_IGNORE(double, ST_KEY_ROTATION_Y, ST_KEY_ROTATION_Y);
|
|
}
|
|
|
|
if (version < STREAMFX_MAKE_VERSION(0, 11, 0, 0)) {
|
|
COPY_UNSET(int, ST_KEY_CAMERA_MODE, "Filter.Transform.Camera");
|
|
COPY_UNSET(double, ST_KEY_CAMERA_FIELDOFVIEW, "Filter.Transform.Camera.FieldOfView");
|
|
COPY_UNSET(double, ST_KEY_POSITION_X, "Filter.Transform.Position.X");
|
|
COPY_UNSET(double, ST_KEY_POSITION_Y, "Filter.Transform.Position.Y");
|
|
COPY_UNSET(double, ST_KEY_POSITION_Z, "Filter.Transform.Position.Z");
|
|
COPY_UNSET(double, ST_KEY_ROTATION_X, "Filter.Transform.Rotation.X");
|
|
COPY_UNSET(double, ST_KEY_ROTATION_Y, "Filter.Transform.Rotation.Y");
|
|
COPY_UNSET(double, ST_KEY_ROTATION_Z, "Filter.Transform.Rotation.Z");
|
|
COPY_UNSET(double, ST_KEY_SCALE_X, "Filter.Transform.Scale.X");
|
|
COPY_UNSET(double, ST_KEY_SCALE_Y, "Filter.Transform.Scale.Y");
|
|
COPY_UNSET(double, ST_KEY_SHEAR_X, "Filter.Transform.Shear.X");
|
|
COPY_UNSET(double, ST_KEY_SHEAR_Y, "Filter.Transform.Shear.Y");
|
|
COPY_UNSET(double, ST_KEY_ROTATION_ORDER, "Filter.Transform.Rotation.Order");
|
|
COPY_UNSET(double, ST_KEY_MIPMAPPING, "Filter.Transform.Mipmapping");
|
|
|
|
if (!obs_data_has_user_value(settings, ST_KEY_CAMERA_MODE)) {
|
|
SET_IF_UNSET(int, ST_KEY_CAMERA_MODE, static_cast<int>(transform_mode::ORTHOGRAPHIC));
|
|
}
|
|
}
|
|
|
|
#undef SET_IF_UNSET
|
|
#undef COPY_IGNORE
|
|
#undef COPY_UNSET
|
|
}
|
|
|
|
void transform_instance::update(obs_data_t* settings)
|
|
{
|
|
// Camera
|
|
_camera_mode = static_cast<transform_mode>(obs_data_get_int(settings, ST_KEY_CAMERA_MODE));
|
|
_camera_fov = static_cast<float>(obs_data_get_double(settings, ST_KEY_CAMERA_FIELDOFVIEW));
|
|
|
|
{ // Parametrized Mesh
|
|
_params.position.x = static_cast<float>(obs_data_get_double(settings, ST_KEY_POSITION_X) / 100.0);
|
|
_params.position.y = static_cast<float>(obs_data_get_double(settings, ST_KEY_POSITION_Y) / 100.0);
|
|
_params.position.z = static_cast<float>(obs_data_get_double(settings, ST_KEY_POSITION_Z) / 100.0);
|
|
_params.scale.x = static_cast<float>(obs_data_get_double(settings, ST_KEY_SCALE_X) / 100.0);
|
|
_params.scale.y = static_cast<float>(obs_data_get_double(settings, ST_KEY_SCALE_Y) / 100.0);
|
|
_params.scale.z = 1.0f;
|
|
_params.rotation_order = static_cast<uint32_t>(obs_data_get_int(settings, ST_KEY_ROTATION_ORDER));
|
|
_params.rotation.x = static_cast<float>(obs_data_get_double(settings, ST_KEY_ROTATION_X) / 180.0 * S_PI);
|
|
_params.rotation.y = static_cast<float>(obs_data_get_double(settings, ST_KEY_ROTATION_Y) / 180.0 * S_PI);
|
|
_params.rotation.z = static_cast<float>(obs_data_get_double(settings, ST_KEY_ROTATION_Z) / 180.0 * S_PI);
|
|
_params.shear.x = static_cast<float>(obs_data_get_double(settings, ST_KEY_SHEAR_X) / 100.0);
|
|
_params.shear.y = static_cast<float>(obs_data_get_double(settings, ST_KEY_SHEAR_Y) / 100.0);
|
|
_params.shear.z = 0.0f;
|
|
}
|
|
{ // Corners
|
|
std::pair<std::string, float&> opts[] = {
|
|
{ST_KEY_CORNERS_TOPLEFT "X", _corners.tl.x}, {ST_KEY_CORNERS_TOPLEFT "Y", _corners.tl.y}, {ST_KEY_CORNERS_TOPRIGHT "X", _corners.tr.x}, {ST_KEY_CORNERS_TOPRIGHT "Y", _corners.tr.y}, {ST_KEY_CORNERS_BOTTOMLEFT "X", _corners.bl.x}, {ST_KEY_CORNERS_BOTTOMLEFT "Y", _corners.bl.y}, {ST_KEY_CORNERS_BOTTOMRIGHT "X", _corners.br.x}, {ST_KEY_CORNERS_BOTTOMRIGHT "Y", _corners.br.y},
|
|
};
|
|
for (auto opt : opts) {
|
|
opt.second = static_cast<float>(obs_data_get_double(settings, opt.first.c_str()) / 100.0);
|
|
}
|
|
}
|
|
|
|
// Mip-mapping
|
|
_mipmap_enabled = obs_data_get_bool(settings, ST_KEY_MIPMAPPING);
|
|
_sampler.set_filter(_mipmap_enabled ? GS_FILTER_ANISOTROPIC : GS_FILTER_LINEAR);
|
|
|
|
_update_mesh = true;
|
|
}
|
|
|
|
void transform_instance::video_tick(float)
|
|
{
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
|
|
// Grab parent and target.
|
|
obs_source_t* target = obs_filter_get_target(_self);
|
|
if (target) {
|
|
// Grab width an height of the target source (child filter or source).
|
|
width = obs_source_get_base_width(target);
|
|
height = obs_source_get_base_height(target);
|
|
}
|
|
|
|
// If size mismatch, force an update.
|
|
if (width != _source_size.first) {
|
|
_update_mesh = true;
|
|
} else if (height != _source_size.second) {
|
|
_update_mesh = true;
|
|
}
|
|
|
|
// Update Mesh
|
|
if (_update_mesh) {
|
|
_source_size.first = width;
|
|
_source_size.second = height;
|
|
|
|
if (width == 0) {
|
|
width = 1;
|
|
}
|
|
if (height == 0) {
|
|
height = 1;
|
|
}
|
|
|
|
if (_camera_mode != transform_mode::CORNER_PIN) {
|
|
// Calculate Aspect Ratio
|
|
float aspect_ratio_x = float(width) / float(height);
|
|
if (_camera_mode == transform_mode::ORTHOGRAPHIC)
|
|
aspect_ratio_x = 1.0;
|
|
|
|
// Mesh
|
|
matrix4 ident;
|
|
matrix4_identity(&ident);
|
|
switch (_params.rotation_order) {
|
|
case RotationOrder::XYZ: // XYZ
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
break;
|
|
case RotationOrder::XZY: // XZY
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
break;
|
|
case RotationOrder::YXZ: // YXZ
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
break;
|
|
case RotationOrder::YZX: // YZX
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
break;
|
|
case RotationOrder::ZXY: // ZXY
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
break;
|
|
case RotationOrder::ZYX: // ZYX
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, _params.rotation.z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, _params.rotation.y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, _params.rotation.x);
|
|
break;
|
|
}
|
|
matrix4_translate3f(&ident, &ident, _params.position.x, _params.position.y, _params.position.z);
|
|
//matrix4_scale3f(&ident, &ident, _source_size.first / 2.f, _source_size.second / 2.f, 1.f);
|
|
|
|
/// Calculate vertex position once only.
|
|
float p_x = aspect_ratio_x * _params.scale.x;
|
|
float p_y = 1.0f * _params.scale.y;
|
|
|
|
/// Generate mesh
|
|
{
|
|
auto vtx = _vertex_buffer->at(0);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 0, 0, 0, 0);
|
|
vec3_set(vtx.position, -p_x + _params.shear.x, -p_y - _params.shear.y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = _vertex_buffer->at(1);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 1, 0, 0, 0);
|
|
vec3_set(vtx.position, p_x + _params.shear.x, -p_y + _params.shear.y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = _vertex_buffer->at(2);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 0, 1, 0, 0);
|
|
vec3_set(vtx.position, -p_x - _params.shear.x, p_y - _params.shear.y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = _vertex_buffer->at(3);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 1, 1, 0, 0);
|
|
vec3_set(vtx.position, p_x - _params.shear.x, p_y + _params.shear.y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
} else if (_camera_mode == transform_mode::CORNER_PIN) {
|
|
// Corner Pin is rendered in Fragment.
|
|
}
|
|
|
|
_vertex_buffer->update(true);
|
|
_update_mesh = false;
|
|
}
|
|
|
|
_cache_rendered = false;
|
|
_mipmap_rendered = false;
|
|
_source_rendered = false;
|
|
}
|
|
|
|
void transform_instance::video_render(gs_effect_t* effect)
|
|
{
|
|
obs_source_t* parent = obs_filter_get_parent(_self);
|
|
obs_source_t* target = obs_filter_get_target(_self);
|
|
uint32_t base_width = obs_source_get_base_width(target);
|
|
uint32_t base_height = obs_source_get_base_height(target);
|
|
gs_effect_t* default_effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
|
if (!effect)
|
|
effect = default_effect;
|
|
|
|
if (!base_width || !base_height || !parent || !target || !_standard_effect || !_transform_effect) { // Skip if something is wrong.
|
|
obs_source_skip_video_filter(_self);
|
|
return;
|
|
}
|
|
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdmp{streamfx::obs::gs::debug_color_source, "3D Transform '%s' on '%s'", obs_source_get_name(_self), obs_source_get_name(obs_filter_get_parent(_self))};
|
|
#endif
|
|
|
|
uint32_t cache_width = base_width;
|
|
uint32_t cache_height = base_height;
|
|
|
|
if (_mipmap_enabled) {
|
|
double_t aspect = double_t(base_width) / double_t(base_height);
|
|
double_t aspect2 = 1.0 / aspect;
|
|
cache_width = std::clamp(uint32_t(pow(2, streamfx::util::math::get_power_of_two_exponent_ceil(cache_width))), 1u, 16384u);
|
|
cache_height = std::clamp(uint32_t(pow(2, streamfx::util::math::get_power_of_two_exponent_ceil(cache_height))), 1u, 16384u);
|
|
|
|
if (aspect > 1.0) {
|
|
cache_height = std::clamp(uint32_t(pow(2, streamfx::util::math::get_power_of_two_exponent_ceil(uint64_t(cache_width * aspect2)))), 1u, 16384u);
|
|
} else if (aspect < 1.0) {
|
|
cache_width = std::clamp(uint32_t(pow(2, streamfx::util::math::get_power_of_two_exponent_ceil(uint64_t(cache_height * aspect)))), 1u, 16384u);
|
|
}
|
|
}
|
|
|
|
if (!_cache_rendered) {
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdm{streamfx::obs::gs::debug_color_cache, "Cache"};
|
|
#endif
|
|
|
|
auto op = _cache_rt->render(cache_width, cache_height);
|
|
|
|
gs_ortho(0, static_cast<float>(base_width), 0, static_cast<float>(base_height), -1, 1);
|
|
|
|
vec4 clear_color = {0, 0, 0, 0};
|
|
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &clear_color, 0, 0);
|
|
|
|
/// Render original source
|
|
if (obs_source_process_filter_begin(_self, GS_RGBA, OBS_NO_DIRECT_RENDERING)) {
|
|
gs_blend_state_push();
|
|
gs_reset_blend_state();
|
|
gs_enable_blending(false);
|
|
gs_blend_function_separate(GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_SRCALPHA, GS_BLEND_ZERO);
|
|
gs_enable_depth_test(false);
|
|
gs_enable_stencil_test(false);
|
|
gs_enable_stencil_write(false);
|
|
gs_enable_color(true, true, true, true);
|
|
gs_set_cull_mode(GS_NEITHER);
|
|
|
|
obs_source_process_filter_end(_self, default_effect, base_width, base_height);
|
|
|
|
gs_blend_state_pop();
|
|
} else {
|
|
obs_source_skip_video_filter(_self);
|
|
return;
|
|
}
|
|
|
|
_cache_rendered = true;
|
|
}
|
|
_cache_rt->get_texture(_cache_texture);
|
|
if (!_cache_texture) {
|
|
obs_source_skip_video_filter(_self);
|
|
return;
|
|
}
|
|
|
|
if (_mipmap_enabled) {
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdm{streamfx::obs::gs::debug_color_convert, "Mipmap"};
|
|
#endif
|
|
|
|
if (!_mipmap_texture || (_mipmap_texture->get_width() != cache_width) || (_mipmap_texture->get_height() != cache_height)) {
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdr{streamfx::obs::gs::debug_color_allocate, "Allocate Mipmapped Texture"};
|
|
#endif
|
|
|
|
std::size_t mip_levels = _mipmapper.calculate_max_mip_level(cache_width, cache_height);
|
|
_mipmap_texture = std::make_shared<streamfx::obs::gs::texture>(cache_width, cache_height, GS_RGBA, static_cast<uint32_t>(mip_levels), nullptr, streamfx::obs::gs::texture::flags::None);
|
|
}
|
|
_mipmapper.rebuild(_cache_texture, _mipmap_texture);
|
|
|
|
_mipmap_rendered = true;
|
|
if (!_mipmap_texture) {
|
|
obs_source_skip_video_filter(_self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdm{streamfx::obs::gs::debug_color_convert, "Transform"};
|
|
#endif
|
|
|
|
auto op = _source_rt->render(base_width, base_height);
|
|
|
|
vec4 clear_color = {0, 0, 0, 0};
|
|
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &clear_color, 0, 0);
|
|
|
|
gs_blend_state_push();
|
|
gs_reset_blend_state();
|
|
gs_enable_blending(false);
|
|
gs_blend_function_separate(GS_BLEND_ONE, GS_BLEND_ZERO, GS_BLEND_ONE, GS_BLEND_ZERO);
|
|
|
|
gs_enable_depth_test(false);
|
|
gs_enable_stencil_test(false);
|
|
gs_enable_stencil_write(false);
|
|
gs_enable_color(true, true, true, true);
|
|
gs_set_cull_mode(GS_NEITHER);
|
|
|
|
switch (_camera_mode) {
|
|
case transform_mode::ORTHOGRAPHIC:
|
|
gs_ortho(-1., 1., -1., 1., -farZ, farZ);
|
|
break;
|
|
case transform_mode::PERSPECTIVE:
|
|
gs_perspective(_camera_fov, float(base_width) / float(base_height), nearZ, farZ);
|
|
gs_matrix_scale3f(1.0, 1.0, 1.0);
|
|
gs_matrix_translate3f(0., 0., -1.0);
|
|
break;
|
|
case transform_mode::CORNER_PIN:
|
|
gs_ortho(0., 1., 0., 1., -farZ, farZ);
|
|
break;
|
|
}
|
|
|
|
if (_camera_mode != transform_mode::CORNER_PIN) {
|
|
gs_load_vertexbuffer(_vertex_buffer->update(false));
|
|
gs_load_indexbuffer(nullptr);
|
|
if (auto v = _standard_effect.get_parameter("InputA"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Texture) {
|
|
v.set_texture(_mipmap_enabled ? (_mipmap_texture ? _mipmap_texture->get_object() : _cache_texture->get_object()) : _cache_texture->get_object());
|
|
v.set_sampler(_sampler.get_object());
|
|
}
|
|
while (gs_effect_loop(_standard_effect.get_object(), "Draw")) {
|
|
gs_draw(GS_TRISTRIP, 0, _vertex_buffer->size());
|
|
}
|
|
gs_load_vertexbuffer(nullptr);
|
|
} else {
|
|
gs_load_vertexbuffer(nullptr);
|
|
gs_load_indexbuffer(nullptr);
|
|
if (auto v = _transform_effect.get_parameter("InputA"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Texture) {
|
|
v.set_texture(_mipmap_enabled ? (_mipmap_texture ? _mipmap_texture->get_object() : _cache_texture->get_object()) : _cache_texture->get_object());
|
|
v.set_sampler(_sampler.get_object());
|
|
}
|
|
if (auto v = _transform_effect.get_parameter("CornerTL"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Float2) {
|
|
v.set_float2(_corners.tl);
|
|
}
|
|
if (auto v = _transform_effect.get_parameter("CornerTR"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Float2) {
|
|
v.set_float2(_corners.tr);
|
|
}
|
|
if (auto v = _transform_effect.get_parameter("CornerBL"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Float2) {
|
|
v.set_float2(_corners.bl);
|
|
}
|
|
if (auto v = _transform_effect.get_parameter("CornerBR"); v.get_type() == ::streamfx::obs::gs::effect_parameter::type::Float2) {
|
|
v.set_float2(_corners.br);
|
|
}
|
|
while (gs_effect_loop(_transform_effect.get_object(), "CornerPin")) {
|
|
_gfx_util->draw_fullscreen_triangle();
|
|
}
|
|
}
|
|
|
|
gs_blend_state_pop();
|
|
}
|
|
_source_rt->get_texture(_source_texture);
|
|
if (!_source_texture) {
|
|
obs_source_skip_video_filter(_self);
|
|
return;
|
|
}
|
|
|
|
{
|
|
#if defined(ENABLE_PROFILING) && !defined(D_PLATFORM_MAC) && _DEBUG
|
|
streamfx::obs::gs::debug_marker gdm{streamfx::obs::gs::debug_color_render, "Render"};
|
|
#endif
|
|
|
|
gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), _source_texture->get_object());
|
|
while (gs_effect_loop(effect, "Draw")) {
|
|
gs_draw_sprite(nullptr, 0, base_width, base_height);
|
|
}
|
|
}
|
|
}
|
|
|
|
transform_factory::transform_factory()
|
|
{
|
|
_info.id = S_PREFIX "filter-transform";
|
|
_info.type = OBS_SOURCE_TYPE_FILTER;
|
|
_info.output_flags = OBS_SOURCE_VIDEO;
|
|
|
|
support_size(false);
|
|
finish_setup();
|
|
register_proxy("obs-stream-effects-filter-transform");
|
|
}
|
|
|
|
transform_factory::~transform_factory() {}
|
|
|
|
const char* transform_factory::get_name()
|
|
{
|
|
return D_TRANSLATE(ST_I18N);
|
|
}
|
|
|
|
void transform_factory::get_defaults2(obs_data_t* settings)
|
|
{
|
|
obs_data_set_default_int(settings, ST_KEY_CAMERA_MODE, static_cast<int64_t>(transform_mode::CORNER_PIN));
|
|
obs_data_set_default_double(settings, ST_KEY_CAMERA_FIELDOFVIEW, 90.0);
|
|
obs_data_set_default_double(settings, ST_KEY_POSITION_X, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_POSITION_Y, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_POSITION_Z, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_ROTATION_X, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_ROTATION_Y, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_ROTATION_Z, 0);
|
|
obs_data_set_default_int(settings, ST_KEY_ROTATION_ORDER, static_cast<int64_t>(RotationOrder::ZXY));
|
|
obs_data_set_default_double(settings, ST_KEY_SCALE_X, 100);
|
|
obs_data_set_default_double(settings, ST_KEY_SCALE_Y, 100);
|
|
obs_data_set_default_double(settings, ST_KEY_SHEAR_X, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_SHEAR_Y, 0);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_TOPLEFT "X", -100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_TOPLEFT "Y", -100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_TOPRIGHT "X", 100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_TOPRIGHT "Y", -100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_BOTTOMLEFT "X", -100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_BOTTOMLEFT "Y", 100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_BOTTOMRIGHT "X", 100.);
|
|
obs_data_set_default_double(settings, ST_KEY_CORNERS_BOTTOMRIGHT "Y", 100.);
|
|
obs_data_set_default_bool(settings, ST_KEY_MIPMAPPING, false);
|
|
}
|
|
|
|
static bool modified_camera_mode(obs_properties_t* pr, obs_property_t*, obs_data_t* d) noexcept
|
|
{
|
|
try {
|
|
auto mode = static_cast<transform_mode>(obs_data_get_int(d, ST_KEY_CAMERA_MODE));
|
|
bool is_camera = mode != transform_mode::CORNER_PIN;
|
|
bool is_perspective = (mode == transform_mode::PERSPECTIVE) && is_camera;
|
|
bool is_orthographic = (mode == transform_mode::ORTHOGRAPHIC) && is_camera;
|
|
|
|
obs_property_set_visible(obs_properties_get(pr, ST_KEY_CAMERA_FIELDOFVIEW), is_perspective);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_I18N_POSITION), is_camera);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_KEY_POSITION_Z), is_perspective);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_I18N_ROTATION), is_camera);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_I18N_SCALE), is_camera);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_I18N_SHEAR), is_camera);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_KEY_ROTATION_ORDER), is_camera);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_I18N_CORNERS), !is_camera);
|
|
|
|
return true;
|
|
} catch (const std::exception& ex) {
|
|
DLOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what());
|
|
return true;
|
|
} catch (...) {
|
|
DLOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
obs_properties_t* transform_factory::get_properties2(transform_instance* data)
|
|
{
|
|
obs_properties_t* pr = obs_properties_create();
|
|
|
|
#ifdef ENABLE_FRONTEND
|
|
{
|
|
obs_properties_add_button2(pr, S_MANUAL_OPEN, D_TRANSLATE(S_MANUAL_OPEN), streamfx::filter::transform::transform_factory::on_manual_open, nullptr);
|
|
}
|
|
#endif
|
|
|
|
// Camera
|
|
{
|
|
auto grp = obs_properties_create();
|
|
|
|
{ // Projection Mode
|
|
auto p = obs_properties_add_list(grp, ST_KEY_CAMERA_MODE, D_TRANSLATE(ST_I18N_CAMERA_MODE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_CAMERA_MODE_CORNER_PIN), static_cast<int64_t>(transform_mode::CORNER_PIN));
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_CAMERA_MODE_ORTHOGRAPHIC), static_cast<int64_t>(transform_mode::ORTHOGRAPHIC));
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_CAMERA_MODE_PERSPECTIVE), static_cast<int64_t>(transform_mode::PERSPECTIVE));
|
|
obs_property_set_modified_callback(p, modified_camera_mode);
|
|
}
|
|
{ // Field Of View
|
|
auto p = obs_properties_add_float_slider(grp, ST_KEY_CAMERA_FIELDOFVIEW, D_TRANSLATE(ST_I18N_CAMERA_FIELDOFVIEW), 1.0, 179.0, 0.01);
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_CAMERA, D_TRANSLATE(ST_I18N_CAMERA), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
|
|
{
|
|
; // Parmametrized Mesh
|
|
|
|
{ // Position
|
|
auto grp = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_POSITION_X, "X"},
|
|
{ST_KEY_POSITION_Y, "Y"},
|
|
{ST_KEY_POSITION_Z, "Z"},
|
|
};
|
|
for (const auto& opt : opts) {
|
|
auto p = obs_properties_add_float(grp, opt.first.c_str(), D_TRANSLATE(opt.second.c_str()), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max(), 0.01);
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_POSITION, D_TRANSLATE(ST_I18N_POSITION), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
{ // Rotation
|
|
auto grp = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_ROTATION_X, D_TRANSLATE(ST_I18N_ROTATION ".X")},
|
|
{ST_KEY_ROTATION_Y, D_TRANSLATE(ST_I18N_ROTATION ".Y")},
|
|
{ST_KEY_ROTATION_Z, D_TRANSLATE(ST_I18N_ROTATION ".Z")},
|
|
};
|
|
for (const auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp, opt.first.c_str(), D_TRANSLATE(opt.second.c_str()), -180.0, 180.0, 0.01);
|
|
obs_property_float_set_suffix(p, "° Deg");
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_ROTATION, D_TRANSLATE(ST_I18N_ROTATION), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
{ // Scale
|
|
auto grp = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_SCALE_X, "X"},
|
|
{ST_KEY_SCALE_Y, "Y"},
|
|
};
|
|
for (const auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp, opt.first.c_str(), opt.second.c_str(), -1000, 1000, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_SCALE, D_TRANSLATE(ST_I18N_SCALE), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
{ // Shear
|
|
auto grp = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_SHEAR_X, "X"},
|
|
{ST_KEY_SHEAR_Y, "Y"},
|
|
};
|
|
for (const auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp, opt.first.c_str(), opt.second.c_str(), -200.0, 200.0, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_SHEAR, D_TRANSLATE(ST_I18N_SHEAR), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
}
|
|
|
|
{ // Corners
|
|
|
|
auto grp = obs_properties_create();
|
|
{ // Top Left
|
|
auto grp2 = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_CORNERS_TOPLEFT "X", "X"},
|
|
{ST_KEY_CORNERS_TOPLEFT "Y", "Y"},
|
|
};
|
|
for (auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp2, opt.first.c_str(), opt.second.c_str(), -200.0, 200.0, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(grp, ST_I18N_CORNERS_TOPLEFT, D_TRANSLATE(ST_I18N_CORNERS_TOPLEFT), OBS_GROUP_NORMAL, grp2);
|
|
}
|
|
{ // Top Right
|
|
auto grp2 = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_CORNERS_TOPRIGHT "X", "X"},
|
|
{ST_KEY_CORNERS_TOPRIGHT "Y", "Y"},
|
|
};
|
|
for (auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp2, opt.first.c_str(), opt.second.c_str(), -200.0, 200.0, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(grp, ST_I18N_CORNERS_TOPRIGHT, D_TRANSLATE(ST_I18N_CORNERS_TOPRIGHT), OBS_GROUP_NORMAL, grp2);
|
|
}
|
|
{ // Bottom Left
|
|
auto grp2 = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_CORNERS_BOTTOMLEFT "X", "X"},
|
|
{ST_KEY_CORNERS_BOTTOMLEFT "Y", "Y"},
|
|
};
|
|
for (auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp2, opt.first.c_str(), opt.second.c_str(), -200.0, 200.0, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(grp, ST_I18N_CORNERS_BOTTOMLEFT, D_TRANSLATE(ST_I18N_CORNERS_BOTTOMLEFT), OBS_GROUP_NORMAL, grp2);
|
|
}
|
|
{ // Bottom Right
|
|
auto grp2 = obs_properties_create();
|
|
|
|
std::pair<std::string, std::string> opts[] = {
|
|
{ST_KEY_CORNERS_BOTTOMRIGHT "X", "X"},
|
|
{ST_KEY_CORNERS_BOTTOMRIGHT "Y", "Y"},
|
|
};
|
|
for (auto& opt : opts) {
|
|
auto p = obs_properties_add_float_slider(grp2, opt.first.c_str(), opt.second.c_str(), -200.0, 200.0, 0.01);
|
|
obs_property_float_set_suffix(p, "%");
|
|
}
|
|
|
|
obs_properties_add_group(grp, ST_I18N_CORNERS_BOTTOMRIGHT, D_TRANSLATE(ST_I18N_CORNERS_BOTTOMRIGHT), OBS_GROUP_NORMAL, grp2);
|
|
}
|
|
|
|
obs_properties_add_group(pr, ST_I18N_CORNERS, D_TRANSLATE(ST_I18N_CORNERS), OBS_GROUP_NORMAL, grp);
|
|
}
|
|
|
|
{
|
|
auto grp = obs_properties_create();
|
|
obs_properties_add_group(pr, S_ADVANCED, D_TRANSLATE(S_ADVANCED), OBS_GROUP_NORMAL, grp);
|
|
|
|
{ // Mip-mapping
|
|
auto p = obs_properties_add_bool(grp, ST_KEY_MIPMAPPING, D_TRANSLATE(ST_I18N_MIPMAPPING));
|
|
}
|
|
|
|
{ // Order
|
|
auto p = obs_properties_add_list(grp, ST_KEY_ROTATION_ORDER, D_TRANSLATE(ST_I18N_ROTATION_ORDER), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_XYZ), RotationOrder::XYZ);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_XZY), RotationOrder::XZY);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_YXZ), RotationOrder::YXZ);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_YZX), RotationOrder::YZX);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_ZXY), RotationOrder::ZXY);
|
|
obs_property_list_add_int(p, D_TRANSLATE(ST_I18N_ROTATION_ORDER_ZYX), RotationOrder::ZYX);
|
|
}
|
|
}
|
|
|
|
return pr;
|
|
}
|
|
|
|
#ifdef ENABLE_FRONTEND
|
|
bool transform_factory::on_manual_open(obs_properties_t* props, obs_property_t* property, void* data)
|
|
{
|
|
try {
|
|
streamfx::open_url(HELP_URL);
|
|
return false;
|
|
} catch (const std::exception& ex) {
|
|
D_LOG_ERROR("Failed to open manual due to error: %s", ex.what());
|
|
return false;
|
|
} catch (...) {
|
|
D_LOG_ERROR("Failed to open manual due to unknown error.", "");
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
std::shared_ptr<transform_factory> transform_factory::instance()
|
|
{
|
|
static std::weak_ptr<transform_factory> winst;
|
|
static std::mutex mtx;
|
|
|
|
std::unique_lock<decltype(mtx)> lock(mtx);
|
|
auto instance = winst.lock();
|
|
if (!instance) {
|
|
instance = std::shared_ptr<transform_factory>(new transform_factory());
|
|
winst = instance;
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
static std::shared_ptr<transform_factory> loader_instance;
|
|
|
|
static auto loader = streamfx::loader(
|
|
[]() { // Initalizer
|
|
loader_instance = transform_factory::instance();
|
|
},
|
|
[]() { // Finalizer
|
|
loader_instance.reset();
|
|
},
|
|
streamfx::loader_priority::NORMAL);
|