mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-11-10 22:05:06 +00:00
0ae120835a
The Transform filter will now no longer render the child source more than once per tick(), resulting in an overall speed up for heavy sources. This also applies to mipmapping and shape rendering. Any other calls to video_render will instead just use the cached texture. Additionally the crash on exit has been fixed which was caused by strings.hpp containing static const char*s and using these directly in obs calls. Instead we now use #define for those property names
642 lines
22 KiB
C++
642 lines
22 KiB
C++
/*
|
|
* 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-transform.hpp"
|
|
#include "strings.hpp"
|
|
#include "util-math.hpp"
|
|
|
|
// OBS
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4201)
|
|
#endif
|
|
#include <graphics/graphics.h>
|
|
#include <graphics/matrix4.h>
|
|
#include <util/platform.h>
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
// Initializer & Finalizer
|
|
static filter::TransformAddon* filterTransformInstance;
|
|
INITIALIZER(FilterTransformInit)
|
|
{
|
|
initializerFunctions.push_back([] { filterTransformInstance = new filter::TransformAddon(); });
|
|
finalizerFunctions.push_back([] { delete filterTransformInstance; });
|
|
}
|
|
|
|
#define ST "Filter.Transform"
|
|
#define ST_CAMERA "Filter.Transform.Camera"
|
|
#define ST_CAMERA_ORTHOGRAPHIC "Filter.Transform.Camera.Orthographic"
|
|
#define ST_CAMERA_PERSPECTIVE "Filter.Transform.Camera.Perspective"
|
|
#define ST_CAMERA_FIELDOFVIEW "Filter.Transform.Camera.FieldOfView"
|
|
#define ST_POSITION "Filter.Transform.Position"
|
|
#define ST_POSITION_X "Filter.Transform.Position.X"
|
|
#define ST_POSITION_Y "Filter.Transform.Position.Y"
|
|
#define ST_POSITION_Z "Filter.Transform.Position.Z"
|
|
#define ST_ROTATION "Filter.Transform.Rotation"
|
|
#define ST_ROTATION_X "Filter.Transform.Rotation.X"
|
|
#define ST_ROTATION_Y "Filter.Transform.Rotation.Y"
|
|
#define ST_ROTATION_Z "Filter.Transform.Rotation.Z"
|
|
#define ST_SCALE "Filter.Transform.Scale"
|
|
#define ST_SCALE_X "Filter.Transform.Scale.X"
|
|
#define ST_SCALE_Y "Filter.Transform.Scale.Y"
|
|
#define ST_SHEAR "Filter.Transform.Shear"
|
|
#define ST_SHEAR_X "Filter.Transform.Shear.X"
|
|
#define ST_SHEAR_Y "Filter.Transform.Shear.Y"
|
|
#define ST_ROTATION_ORDER "Filter.Transform.Rotation.Order"
|
|
#define ST_ROTATION_ORDER_XYZ "Filter.Transform.Rotation.Order.XYZ"
|
|
#define ST_ROTATION_ORDER_XZY "Filter.Transform.Rotation.Order.XZY"
|
|
#define ST_ROTATION_ORDER_YXZ "Filter.Transform.Rotation.Order.YXZ"
|
|
#define ST_ROTATION_ORDER_YZX "Filter.Transform.Rotation.Order.YZX"
|
|
#define ST_ROTATION_ORDER_ZXY "Filter.Transform.Rotation.Order.ZXY"
|
|
#define ST_ROTATION_ORDER_ZYX "Filter.Transform.Rotation.Order.ZYX"
|
|
#define ST_MIPMAPPING "Filter.Transform.Mipmapping"
|
|
|
|
static const float farZ = 2097152.0f; // 2 pow 21
|
|
static const float nearZ = 1.0f / farZ;
|
|
|
|
enum class CameraMode : int32_t { Orthographic, Perspective };
|
|
|
|
enum RotationOrder : int64_t {
|
|
XYZ,
|
|
XZY,
|
|
YXZ,
|
|
YZX,
|
|
ZXY,
|
|
ZYX,
|
|
};
|
|
|
|
filter::TransformAddon::TransformAddon()
|
|
{
|
|
memset(&sourceInfo, 0, sizeof(obs_source_info));
|
|
sourceInfo.id = "obs-stream-effects-filter-transform";
|
|
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::TransformAddon::~TransformAddon() {}
|
|
|
|
const char* filter::TransformAddon::get_name(void*)
|
|
{
|
|
return P_TRANSLATE(ST);
|
|
}
|
|
|
|
void filter::TransformAddon::get_defaults(obs_data_t* data)
|
|
{
|
|
obs_data_set_default_int(data, ST_CAMERA, (int64_t)CameraMode::Orthographic);
|
|
obs_data_set_default_double(data, ST_CAMERA_FIELDOFVIEW, 90.0);
|
|
obs_data_set_default_double(data, ST_POSITION_X, 0);
|
|
obs_data_set_default_double(data, ST_POSITION_Y, 0);
|
|
obs_data_set_default_double(data, ST_POSITION_Z, 0);
|
|
obs_data_set_default_double(data, ST_ROTATION_X, 0);
|
|
obs_data_set_default_double(data, ST_ROTATION_Y, 0);
|
|
obs_data_set_default_double(data, ST_ROTATION_Z, 0);
|
|
obs_data_set_default_double(data, ST_SCALE_X, 100);
|
|
obs_data_set_default_double(data, ST_SCALE_Y, 100);
|
|
obs_data_set_default_double(data, ST_SHEAR_X, 0);
|
|
obs_data_set_default_double(data, ST_SHEAR_Y, 0);
|
|
obs_data_set_default_bool(data, S_ADVANCED, false);
|
|
obs_data_set_default_int(data, ST_ROTATION_ORDER, RotationOrder::ZXY);
|
|
}
|
|
|
|
obs_properties_t* filter::TransformAddon::get_properties(void*)
|
|
{
|
|
obs_properties_t* pr = obs_properties_create();
|
|
obs_property_t* p = NULL;
|
|
|
|
// Camera
|
|
/// Projection Mode
|
|
p = obs_properties_add_list(pr, ST_CAMERA, P_TRANSLATE(ST_CAMERA), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(ST_CAMERA)));
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_CAMERA_ORTHOGRAPHIC), (int64_t)CameraMode::Orthographic);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_CAMERA_PERSPECTIVE), (int64_t)CameraMode::Perspective);
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
/// Field Of View
|
|
p = obs_properties_add_float_slider(pr, ST_CAMERA_FIELDOFVIEW, P_TRANSLATE(ST_CAMERA_FIELDOFVIEW), 1.0, 179.0,
|
|
0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(ST_CAMERA_FIELDOFVIEW)));
|
|
|
|
// Mesh
|
|
/// Position
|
|
{
|
|
std::pair<const char*, const char*> entries[] = {
|
|
std::make_pair(ST_POSITION_X, P_DESC(ST_POSITION_X)),
|
|
std::make_pair(ST_POSITION_Y, P_DESC(ST_POSITION_Y)),
|
|
std::make_pair(ST_POSITION_Z, P_DESC(ST_POSITION_Z)),
|
|
};
|
|
for (auto kv : entries) {
|
|
p = obs_properties_add_float(pr, kv.first, P_TRANSLATE(kv.first), -10000, 10000, 0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(kv.second));
|
|
}
|
|
}
|
|
/// Rotation
|
|
{
|
|
std::pair<const char*, const char*> entries[] = {
|
|
std::make_pair(ST_ROTATION_X, P_DESC(ST_ROTATION_X)),
|
|
std::make_pair(ST_ROTATION_Y, P_DESC(ST_ROTATION_Y)),
|
|
std::make_pair(ST_ROTATION_Z, P_DESC(ST_ROTATION_Z)),
|
|
};
|
|
for (auto kv : entries) {
|
|
p = obs_properties_add_float_slider(pr, kv.first, P_TRANSLATE(kv.first), -180, 180, 0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(kv.second));
|
|
}
|
|
}
|
|
/// Scale
|
|
{
|
|
std::pair<const char*, const char*> entries[] = {
|
|
std::make_pair(ST_SCALE_X, P_DESC(ST_SCALE_X)),
|
|
std::make_pair(ST_SCALE_Y, P_DESC(ST_SCALE_Y)),
|
|
};
|
|
for (auto kv : entries) {
|
|
p = obs_properties_add_float_slider(pr, kv.first, P_TRANSLATE(kv.first), -1000, 1000, 0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(kv.second));
|
|
}
|
|
}
|
|
/// Shear
|
|
{
|
|
std::pair<const char*, const char*> entries[] = {
|
|
std::make_pair(ST_SHEAR_X, P_DESC(ST_SHEAR_X)),
|
|
std::make_pair(ST_SHEAR_Y, P_DESC(ST_SHEAR_Y)),
|
|
};
|
|
for (auto kv : entries) {
|
|
p = obs_properties_add_float_slider(pr, kv.first, P_TRANSLATE(kv.first), -100.0, 100.0, 0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(kv.second));
|
|
}
|
|
}
|
|
|
|
p = obs_properties_add_bool(pr, S_ADVANCED, P_TRANSLATE(S_ADVANCED));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
|
|
p = obs_properties_add_list(pr, ST_ROTATION_ORDER, P_TRANSLATE(ST_ROTATION_ORDER), OBS_COMBO_TYPE_LIST,
|
|
OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(ST_ROTATION_ORDER)));
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_XYZ), RotationOrder::XYZ);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_XZY), RotationOrder::XZY);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_YXZ), RotationOrder::YXZ);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_YZX), RotationOrder::YZX);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_ZXY), RotationOrder::ZXY);
|
|
obs_property_list_add_int(p, P_TRANSLATE(ST_ROTATION_ORDER_ZYX), RotationOrder::ZYX);
|
|
|
|
p = obs_properties_add_bool(pr, ST_MIPMAPPING, P_TRANSLATE(ST_MIPMAPPING));
|
|
obs_property_set_modified_callback(p, modified_properties);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(ST_MIPMAPPING)));
|
|
|
|
p = obs_properties_add_list(pr, S_MIPGENERATOR, P_TRANSLATE(S_MIPGENERATOR),
|
|
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_MIPGENERATOR)));
|
|
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT), (long long)gs::mipmapper::generator::Point);
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT),
|
|
(long long)gs::mipmapper::generator::Linear);
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT),
|
|
(long long)gs::mipmapper::generator::Sharpen);
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT),
|
|
(long long)gs::mipmapper::generator::Smoothen);
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT),
|
|
(long long)gs::mipmapper::generator::Bicubic);
|
|
obs_property_list_add_int(p, P_TRANSLATE(S_MIPGENERATOR_POINT),
|
|
(long long)gs::mipmapper::generator::Lanczos);
|
|
|
|
p = obs_properties_add_float_slider(pr, S_MIPGENERATOR_STRENGTH, P_TRANSLATE(S_MIPGENERATOR_STRENGTH), 0.0,
|
|
1000.0, 0.01);
|
|
obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_MIPGENERATOR_STRENGTH)));
|
|
|
|
return pr;
|
|
}
|
|
|
|
bool filter::TransformAddon::modified_properties(obs_properties_t* pr, obs_property_t*, obs_data_t* d)
|
|
{
|
|
switch ((CameraMode)obs_data_get_int(d, ST_CAMERA)) {
|
|
case CameraMode::Orthographic:
|
|
obs_property_set_visible(obs_properties_get(pr, ST_CAMERA_FIELDOFVIEW), false);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_POSITION_Z), false);
|
|
break;
|
|
case CameraMode::Perspective:
|
|
obs_property_set_visible(obs_properties_get(pr, ST_CAMERA_FIELDOFVIEW), true);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_POSITION_Z), true);
|
|
break;
|
|
}
|
|
|
|
bool advancedVisible = obs_data_get_bool(d, S_ADVANCED);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_ROTATION_ORDER), advancedVisible);
|
|
obs_property_set_visible(obs_properties_get(pr, ST_MIPMAPPING), advancedVisible);
|
|
|
|
bool mipmappingVisible = obs_data_get_bool(d, ST_MIPMAPPING) && advancedVisible;
|
|
obs_property_set_visible(obs_properties_get(pr, S_MIPGENERATOR), mipmappingVisible);
|
|
obs_property_set_visible(obs_properties_get(pr, S_MIPGENERATOR_STRENGTH), mipmappingVisible);
|
|
|
|
return true;
|
|
}
|
|
|
|
void* filter::TransformAddon::create(obs_data_t* data, obs_source_t* source)
|
|
{
|
|
return new Transform(data, source);
|
|
}
|
|
|
|
void filter::TransformAddon::destroy(void* ptr)
|
|
{
|
|
delete reinterpret_cast<Transform*>(ptr);
|
|
}
|
|
|
|
uint32_t filter::TransformAddon::get_width(void* ptr)
|
|
{
|
|
return reinterpret_cast<Transform*>(ptr)->get_width();
|
|
}
|
|
|
|
uint32_t filter::TransformAddon::get_height(void* ptr)
|
|
{
|
|
return reinterpret_cast<Transform*>(ptr)->get_height();
|
|
}
|
|
|
|
void filter::TransformAddon::update(void* ptr, obs_data_t* data)
|
|
{
|
|
reinterpret_cast<Transform*>(ptr)->update(data);
|
|
}
|
|
|
|
void filter::TransformAddon::activate(void* ptr)
|
|
{
|
|
reinterpret_cast<Transform*>(ptr)->activate();
|
|
}
|
|
|
|
void filter::TransformAddon::deactivate(void* ptr)
|
|
{
|
|
reinterpret_cast<Transform*>(ptr)->deactivate();
|
|
}
|
|
|
|
void filter::TransformAddon::video_tick(void* ptr, float time)
|
|
{
|
|
reinterpret_cast<Transform*>(ptr)->video_tick(time);
|
|
}
|
|
|
|
void filter::TransformAddon::video_render(void* ptr, gs_effect_t* effect)
|
|
{
|
|
reinterpret_cast<Transform*>(ptr)->video_render(effect);
|
|
}
|
|
|
|
filter::Transform::~Transform()
|
|
{
|
|
m_shear.reset();
|
|
m_scale.reset();
|
|
m_rotation.reset();
|
|
m_position.reset();
|
|
m_vertex_buffer.reset();
|
|
m_shape_texture.reset();
|
|
m_shape_rendertarget.reset();
|
|
m_source_texture.reset();
|
|
m_source_rendertarget.reset();
|
|
}
|
|
|
|
filter::Transform::Transform(obs_data_t* data, obs_source_t* context)
|
|
: m_active(true), m_self(context), m_source_rendered(false), m_mipmap_enabled(false), m_mipmap_strength(50.0),
|
|
m_mipmap_generator(gs::mipmapper::generator::Linear), m_update_mesh(false), m_rotation_order(RotationOrder::ZXY),
|
|
m_camera_orthographic(true), m_camera_fov(90.0)
|
|
{
|
|
m_source_rendertarget = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
|
m_shape_rendertarget = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
|
|
m_vertex_buffer = std::make_shared<gs::vertex_buffer>(4u, 1u);
|
|
|
|
m_position = std::make_unique<util::vec3a>();
|
|
m_rotation = std::make_unique<util::vec3a>();
|
|
m_scale = std::make_unique<util::vec3a>();
|
|
m_shear = std::make_unique<util::vec3a>();
|
|
|
|
vec3_set(m_position.get(), 0, 0, 0);
|
|
vec3_set(m_rotation.get(), 0, 0, 0);
|
|
vec3_set(m_scale.get(), 1, 1, 1);
|
|
|
|
update(data);
|
|
}
|
|
|
|
uint32_t filter::Transform::get_width()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t filter::Transform::get_height()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void filter::Transform::update(obs_data_t* data)
|
|
{
|
|
// Camera
|
|
m_camera_orthographic = obs_data_get_int(data, ST_CAMERA) == 0;
|
|
m_camera_fov = (float)obs_data_get_double(data, ST_CAMERA_FIELDOFVIEW);
|
|
|
|
// Source
|
|
m_position->x = (float)obs_data_get_double(data, ST_POSITION_X) / 100.0f;
|
|
m_position->y = (float)obs_data_get_double(data, ST_POSITION_Y) / 100.0f;
|
|
m_position->z = (float)obs_data_get_double(data, ST_POSITION_Z) / 100.0f;
|
|
m_scale->x = (float)obs_data_get_double(data, ST_SCALE_X) / 100.0f;
|
|
m_scale->y = (float)obs_data_get_double(data, ST_SCALE_Y) / 100.0f;
|
|
m_scale->z = 1.0f;
|
|
m_rotation_order = (int)obs_data_get_int(data, ST_ROTATION_ORDER);
|
|
m_rotation->x = (float)(obs_data_get_double(data, ST_ROTATION_X) / 180.0f * PI);
|
|
m_rotation->y = (float)(obs_data_get_double(data, ST_ROTATION_Y) / 180.0f * PI);
|
|
m_rotation->z = (float)(obs_data_get_double(data, ST_ROTATION_Z) / 180.0f * PI);
|
|
m_shear->x = (float)obs_data_get_double(data, ST_SHEAR_X) / 100.0f;
|
|
m_shear->y = (float)obs_data_get_double(data, ST_SHEAR_Y) / 100.0f;
|
|
m_shear->z = 0.0f;
|
|
|
|
// Mipmapping
|
|
m_mipmap_enabled = obs_data_get_bool(data, ST_MIPMAPPING);
|
|
m_mipmap_strength = obs_data_get_double(data, S_MIPGENERATOR_STRENGTH);
|
|
m_mipmap_generator = (gs::mipmapper::generator)obs_data_get_int(data, S_MIPGENERATOR);
|
|
|
|
m_update_mesh = true;
|
|
}
|
|
|
|
void filter::Transform::activate()
|
|
{
|
|
m_active = true;
|
|
}
|
|
|
|
void filter::Transform::deactivate()
|
|
{
|
|
m_active = false;
|
|
}
|
|
|
|
void filter::Transform::video_tick(float)
|
|
{
|
|
// Update Mesh
|
|
if (m_update_mesh) {
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
|
|
// Grab parent and target.
|
|
obs_source_t* target = obs_filter_get_target(m_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 (width == 0) {
|
|
width = 1;
|
|
}
|
|
if (height == 0) {
|
|
height = 1;
|
|
}
|
|
|
|
// Calculate Aspect Ratio
|
|
float_t aspectRatioX = float_t(width) / float_t(height);
|
|
if (m_camera_orthographic)
|
|
aspectRatioX = 1.0;
|
|
|
|
// Mesh
|
|
matrix4 ident;
|
|
matrix4_identity(&ident);
|
|
switch (m_rotation_order) {
|
|
case RotationOrder::XYZ: // XYZ
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
break;
|
|
case RotationOrder::XZY: // XZY
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
break;
|
|
case RotationOrder::YXZ: // YXZ
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
break;
|
|
case RotationOrder::YZX: // YZX
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
break;
|
|
case RotationOrder::ZXY: // ZXY
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
break;
|
|
case RotationOrder::ZYX: // ZYX
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, m_rotation->z);
|
|
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, m_rotation->y);
|
|
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, m_rotation->x);
|
|
break;
|
|
}
|
|
matrix4_translate3f(&ident, &ident, m_position->x, m_position->y, m_position->z);
|
|
|
|
/// Calculate vertex position once only.
|
|
float_t p_x = aspectRatioX * m_scale->x;
|
|
float_t p_y = 1.0f * m_scale->y;
|
|
|
|
/// Generate mesh
|
|
{
|
|
auto vtx = m_vertex_buffer->at(0);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 0, 0, 0, 0);
|
|
vec3_set(vtx.position, -p_x + m_shear->x, -p_y - m_shear->y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = m_vertex_buffer->at(1);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 1, 0, 0, 0);
|
|
vec3_set(vtx.position, p_x + m_shear->x, -p_y + m_shear->y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = m_vertex_buffer->at(2);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 0, 1, 0, 0);
|
|
vec3_set(vtx.position, -p_x - m_shear->x, p_y - m_shear->y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
{
|
|
auto vtx = m_vertex_buffer->at(3);
|
|
*vtx.color = 0xFFFFFFFF;
|
|
vec4_set(vtx.uv[0], 1, 1, 0, 0);
|
|
vec3_set(vtx.position, p_x - m_shear->x, p_y + m_shear->y, 0);
|
|
vec3_transform(vtx.position, vtx.position, &ident);
|
|
}
|
|
|
|
m_vertex_buffer->update(true);
|
|
m_update_mesh = false;
|
|
}
|
|
|
|
this->m_source_rendered = false;
|
|
}
|
|
|
|
void filter::Transform::video_render(gs_effect_t* paramEffect)
|
|
{
|
|
if (!m_active) {
|
|
obs_source_skip_video_filter(m_self);
|
|
return;
|
|
}
|
|
|
|
// Grab parent and target.
|
|
obs_source_t* parent = obs_filter_get_parent(m_self);
|
|
obs_source_t* target = obs_filter_get_target(m_self);
|
|
if (!parent || !target) {
|
|
obs_source_skip_video_filter(m_self);
|
|
return;
|
|
}
|
|
|
|
// Grab width an height of the target source (child filter or source).
|
|
uint32_t width = obs_source_get_base_width(target);
|
|
uint32_t height = obs_source_get_base_height(target);
|
|
if ((width == 0) || (height == 0)) {
|
|
obs_source_skip_video_filter(m_self);
|
|
return;
|
|
}
|
|
|
|
gs_effect_t* default_effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
|
|
|
// Only render if we didn't already render.
|
|
if (!this->m_source_rendered) {
|
|
std::shared_ptr<gs::texture> source_tex;
|
|
uint32_t real_width = width;
|
|
uint32_t real_height = height;
|
|
|
|
// If MipMapping is enabled, resize Render Target to be a Power of Two.
|
|
if (m_mipmap_enabled) {
|
|
real_width = uint32_t(pow(2, util::math::get_power_of_two_exponent_ceil(width)));
|
|
real_height = uint32_t(pow(2, util::math::get_power_of_two_exponent_ceil(height)));
|
|
if ((real_width >= 8192) || (real_height >= 8192)) {
|
|
// Most GPUs cap out here, so let's not go higher.
|
|
double_t aspect = double_t(width) / double_t(height);
|
|
if (aspect > 1.0) { // height < width
|
|
real_width = 8192;
|
|
real_height = uint32_t(real_width / aspect);
|
|
} else if (aspect < 1.0) { // width > height
|
|
real_height = 8192;
|
|
real_width = uint32_t(real_height * aspect);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw previous filters to texture.
|
|
try {
|
|
auto op = m_source_rendertarget->render(real_width, real_height);
|
|
|
|
gs_set_cull_mode(GS_NEITHER);
|
|
gs_reset_blend_state();
|
|
gs_blend_function_separate(gs_blend_type::GS_BLEND_ONE, gs_blend_type::GS_BLEND_ZERO,
|
|
gs_blend_type::GS_BLEND_ONE, gs_blend_type::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_ortho(0, (float)width, 0, (float)height, -1, 1);
|
|
|
|
vec4 black;
|
|
vec4_zero(&black);
|
|
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0);
|
|
|
|
/// Render original source
|
|
if (obs_source_process_filter_begin(m_self, GS_RGBA, OBS_NO_DIRECT_RENDERING)) {
|
|
obs_source_process_filter_end(m_self, paramEffect ? paramEffect : default_effect, width, height);
|
|
} else {
|
|
obs_source_skip_video_filter(m_self);
|
|
}
|
|
} catch (...) {
|
|
obs_source_skip_video_filter(m_self);
|
|
return;
|
|
}
|
|
m_source_rendertarget->get_texture(source_tex);
|
|
|
|
if (m_mipmap_enabled) {
|
|
if ((!m_source_texture) || (m_source_texture->get_width() != real_width)
|
|
|| (m_source_texture->get_height() != real_height)) {
|
|
size_t mip_levels = 0;
|
|
if (util::math::is_power_of_two(real_width) && util::math::is_power_of_two(real_height)) {
|
|
size_t w_level = util::math::get_power_of_two_exponent_ceil(real_width);
|
|
size_t h_level = util::math::get_power_of_two_exponent_ceil(real_height);
|
|
if (h_level > w_level) {
|
|
mip_levels = h_level;
|
|
} else {
|
|
mip_levels = w_level;
|
|
}
|
|
}
|
|
|
|
m_source_texture =
|
|
std::make_shared<gs::texture>(real_width, real_height, GS_RGBA, uint32_t(1u + mip_levels), nullptr,
|
|
gs::texture::flags::BuildMipMaps);
|
|
}
|
|
|
|
m_mipmapper.rebuild(source_tex, m_source_texture, m_mipmap_generator, float_t(m_mipmap_strength));
|
|
}
|
|
|
|
// Draw shape to texture
|
|
try {
|
|
auto op = m_shape_rendertarget->render(width, height);
|
|
|
|
if (m_camera_orthographic) {
|
|
gs_ortho(-1.0, 1.0, -1.0, 1.0, -farZ, farZ);
|
|
} else {
|
|
gs_perspective(m_camera_fov, float_t(width) / float_t(height), nearZ, farZ);
|
|
// Fix camera pointing at -Z instead of +Z.
|
|
gs_matrix_scale3f(1.0, 1.0, -1.0);
|
|
// Move backwards so we can actually see stuff.
|
|
gs_matrix_translate3f(0, 0, 1.0);
|
|
}
|
|
|
|
// Rendering
|
|
vec4 black;
|
|
vec4_zero(&black);
|
|
gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, farZ, 0);
|
|
gs_set_cull_mode(GS_NEITHER);
|
|
gs_enable_blending(false);
|
|
gs_enable_depth_test(false);
|
|
gs_depth_function(gs_depth_test::GS_ALWAYS);
|
|
gs_enable_stencil_test(false);
|
|
gs_enable_stencil_write(false);
|
|
gs_enable_color(true, true, true, true);
|
|
gs_load_vertexbuffer(m_vertex_buffer->update(false));
|
|
gs_load_indexbuffer(nullptr);
|
|
while (gs_effect_loop(default_effect, "Draw")) {
|
|
gs_effect_set_texture(gs_effect_get_param_by_name(default_effect, "image"),
|
|
m_mipmap_enabled ? m_source_texture->get_object() : source_tex->get_object());
|
|
gs_draw(GS_TRISTRIP, 0, 4);
|
|
}
|
|
gs_load_vertexbuffer(nullptr);
|
|
} catch (...) {
|
|
obs_source_skip_video_filter(m_self);
|
|
return;
|
|
}
|
|
m_shape_rendertarget->get_texture(m_shape_texture);
|
|
|
|
this->m_source_rendered = true;
|
|
}
|
|
|
|
// Draw final shape
|
|
gs_reset_blend_state();
|
|
gs_enable_depth_test(false);
|
|
while (gs_effect_loop(default_effect, "Draw")) {
|
|
gs_effect_set_texture(gs_effect_get_param_by_name(default_effect, "image"), m_shape_texture->get_object());
|
|
gs_draw_sprite(m_shape_texture->get_object(), 0, 0, 0);
|
|
}
|
|
}
|