obs-StreamFX/source/filter-transform.cpp
Michael Fabian 'Xaymar' Dirks da22e72da7 filter-transform: Fix mipmapping for npot input
The filter will now automatically rescale the parent stack into the next best power of two size. With this, even non-power-of-two sources can now be mipmapped semi-correctly.

To accurately support mipmapping even for npot textures, this feature would have to be built into OBS and OBS would have to stop refusing to create textures with mipmaps that are not a power of two in size. Almost all common Direct3D 11 (except Intel) are capable of npot mipmaps at full speed, while OpenGL usually depends on the GPU and Driver used.
2018-09-29 17:05:58 +02:00

602 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.h"
#include "strings.h"
#include "util-math.h"
extern "C" {
#pragma warning(push)
#pragma warning(disable : 4201)
#include "graphics/graphics.h"
#include "graphics/matrix4.h"
#include "util/platform.h"
#pragma warning(pop)
}
// Initializer & Finalizer
static Filter::Transform* filterTransformInstance;
INITIALIZER(FilterTransformInit)
{
initializerFunctions.push_back([] { filterTransformInstance = new Filter::Transform(); });
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;
static const float valueLimit = 65536.0f;
enum class CameraMode : int32_t { Orthographic, Perspective };
enum RotationOrder : int64_t {
XYZ,
XZY,
YXZ,
YZX,
ZXY,
ZYX,
};
Filter::Transform::Transform()
{
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::Transform::~Transform() {}
const char* Filter::Transform::get_name(void*)
{
return P_TRANSLATE(ST);
}
void Filter::Transform::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); //ZXY
}
obs_properties_t* Filter::Transform::get_properties(void*)
{
obs_properties_t* pr = obs_properties_create();
obs_property_t* p = NULL;
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);
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)));
// Position, Scale, Rotation
{
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));
}
}
{
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));
}
}
{
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));
}
}
{
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, plugin::strings::MipGenerator::Name,
P_TRANSLATE(plugin::strings::MipGenerator::Name), OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_long_description(p, P_TRANSLATE(plugin::strings::MipGenerator::Description));
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Point),
(long long)gs::mipmapper::generator::Point);
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Linear),
(long long)gs::mipmapper::generator::Linear);
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Sharpen),
(long long)gs::mipmapper::generator::Sharpen);
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Smoothen),
(long long)gs::mipmapper::generator::Smoothen);
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Bicubic),
(long long)gs::mipmapper::generator::Bicubic);
obs_property_list_add_int(p, P_TRANSLATE(plugin::strings::MipGenerator::Lanczos),
(long long)gs::mipmapper::generator::Lanczos);
p = obs_properties_add_float(pr, plugin::strings::MipGenerator::Strength,
P_TRANSLATE(plugin::strings::MipGenerator::Strength), 0.0, 100.0, 0.01);
return pr;
}
bool Filter::Transform::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, plugin::strings::MipGenerator::Name), mipmappingVisible);
obs_property_set_visible(obs_properties_get(pr, plugin::strings::MipGenerator::Strength), mipmappingVisible);
return true;
}
void* Filter::Transform::create(obs_data_t* data, obs_source_t* source)
{
return new Instance(data, source);
}
void Filter::Transform::destroy(void* ptr)
{
delete reinterpret_cast<Instance*>(ptr);
}
uint32_t Filter::Transform::get_width(void* ptr)
{
return reinterpret_cast<Instance*>(ptr)->get_width();
}
uint32_t Filter::Transform::get_height(void* ptr)
{
return reinterpret_cast<Instance*>(ptr)->get_height();
}
void Filter::Transform::update(void* ptr, obs_data_t* data)
{
reinterpret_cast<Instance*>(ptr)->update(data);
}
void Filter::Transform::activate(void* ptr)
{
reinterpret_cast<Instance*>(ptr)->activate();
}
void Filter::Transform::deactivate(void* ptr)
{
reinterpret_cast<Instance*>(ptr)->deactivate();
}
void Filter::Transform::video_tick(void* ptr, float time)
{
reinterpret_cast<Instance*>(ptr)->video_tick(time);
}
void Filter::Transform::video_render(void* ptr, gs_effect_t* effect)
{
reinterpret_cast<Instance*>(ptr)->video_render(effect);
}
Filter::Transform::Instance::Instance(obs_data_t* data, obs_source_t* context)
: source_context(context), is_orthographic(true), field_of_view(90.0), is_inactive(false), is_hidden(false),
is_mesh_update_required(false), rotation_order(RotationOrder::ZXY)
{
position = std::make_unique<util::vec3a>();
rotation = std::make_unique<util::vec3a>();
scale = std::make_unique<util::vec3a>();
shear = std::make_unique<util::vec3a>();
vec3_set(position.get(), 0, 0, 0);
vec3_set(rotation.get(), 0, 0, 0);
vec3_set(scale.get(), 1, 1, 1);
enable_mipmapping = false;
generator = gs::mipmapper::generator::Linear;
generator_strength = 50.0;
obs_enter_graphics();
source_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
shape_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
vertex_buffer = std::make_shared<gs::vertex_buffer>(4, 1);
obs_leave_graphics();
update(data);
}
Filter::Transform::Instance::~Instance()
{
obs_enter_graphics();
shape_rt.reset();
source_rt.reset();
vertex_buffer.reset();
obs_leave_graphics();
}
void Filter::Transform::Instance::update(obs_data_t* data)
{
// Camera
is_orthographic = obs_data_get_int(data, ST_CAMERA) == 0;
field_of_view = (float)obs_data_get_double(data, ST_CAMERA_FIELDOFVIEW);
// Source
position->x = (float)obs_data_get_double(data, ST_POSITION_X) / 100.0f;
position->y = (float)obs_data_get_double(data, ST_POSITION_Y) / 100.0f;
position->z = (float)obs_data_get_double(data, ST_POSITION_Z) / 100.0f;
scale->x = (float)obs_data_get_double(data, ST_SCALE_X) / 100.0f;
scale->y = (float)obs_data_get_double(data, ST_SCALE_Y) / 100.0f;
scale->z = 1.0f;
rotation_order = (int)obs_data_get_int(data, ST_ROTATION_ORDER);
rotation->x = (float)(obs_data_get_double(data, ST_ROTATION_X) / 180.0f * PI);
rotation->y = (float)(obs_data_get_double(data, ST_ROTATION_Y) / 180.0f * PI);
rotation->z = (float)(obs_data_get_double(data, ST_ROTATION_Z) / 180.0f * PI);
shear->x = (float)obs_data_get_double(data, ST_SHEAR_X) / 100.0f;
shear->y = (float)obs_data_get_double(data, ST_SHEAR_Y) / 100.0f;
shear->z = 0.0f;
// Mipmapping
enable_mipmapping = obs_data_get_bool(data, ST_MIPMAPPING);
generator_strength = obs_data_get_double(data, plugin::strings::MipGenerator::Strength);
generator = (gs::mipmapper::generator)obs_data_get_int(data, plugin::strings::MipGenerator::Name);
is_mesh_update_required = true;
}
uint32_t Filter::Transform::Instance::get_width()
{
return 0;
}
uint32_t Filter::Transform::Instance::get_height()
{
return 0;
}
void Filter::Transform::Instance::activate()
{
is_inactive = false;
}
void Filter::Transform::Instance::deactivate()
{
is_inactive = true;
}
void Filter::Transform::Instance::video_tick(float) {}
void Filter::Transform::Instance::video_render(gs_effect_t* paramEffect)
{
std::shared_ptr<gs::texture> source_tex;
std::shared_ptr<gs::texture> shape_tex;
obs_source_t* parent = obs_filter_get_parent(source_context);
obs_source_t* target = obs_filter_get_target(source_context);
uint32_t width = obs_source_get_base_width(target);
uint32_t height = obs_source_get_base_height(target);
uint32_t real_width = width;
uint32_t real_height = height;
gs_effect_t* default_effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
// Skip rendering if our target, parent or context is not valid.
if (!target || !parent || !source_context || !width || !height || is_inactive || is_hidden) {
obs_source_skip_video_filter(source_context);
return;
}
// Update Mesh
if (is_mesh_update_required) {
float_t aspectRatioX = float_t(width) / float_t(height);
if (is_orthographic)
aspectRatioX = 1.0;
// Mesh
matrix4 ident;
matrix4_identity(&ident);
switch (rotation_order) {
case RotationOrder::XYZ: // XYZ
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
break;
case RotationOrder::XZY: // XZY
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
break;
case RotationOrder::YXZ: // YXZ
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
break;
case RotationOrder::YZX: // YZX
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
break;
case RotationOrder::ZXY: // ZXY
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
break;
case RotationOrder::ZYX: // ZYX
matrix4_rotate_aa4f(&ident, &ident, 0, 0, 1, rotation->z);
matrix4_rotate_aa4f(&ident, &ident, 0, 1, 0, rotation->y);
matrix4_rotate_aa4f(&ident, &ident, 1, 0, 0, rotation->x);
break;
}
matrix4_translate3f(&ident, &ident, position->x, position->y, position->z);
/// Calculate vertex position once only.
float_t p_x = aspectRatioX * scale->x;
float_t p_y = 1.0f * 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 + shear->x, -p_y - 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 + shear->x, -p_y + 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 - shear->x, p_y - 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 - shear->x, p_y + shear->y, 0);
vec3_transform(vtx.position, vtx.position, &ident);
}
vertex_buffer->update(true);
is_mesh_update_required = false;
}
// Make texture a power of two compatible texture if mipmapping is enabled.
if (enable_mipmapping) {
real_width = pow(2, util::math::get_power_of_two_exponent_ceil(width));
real_height = 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 = real_width / aspect;
} else if (aspect < 1.0) { // width > height
real_height = 8192;
real_width = real_height * aspect;
}
}
}
// Draw previous filters to texture.
try {
auto op = source_rt->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(source_context, GS_RGBA, OBS_NO_DIRECT_RENDERING)) {
obs_source_process_filter_end(source_context, paramEffect ? paramEffect : default_effect, width, height);
} else {
obs_source_skip_video_filter(source_context);
}
} catch (...) {
obs_source_skip_video_filter(source_context);
return;
}
source_rt->get_texture(source_tex);
if (enable_mipmapping) {
if ((!source_texture) || (source_texture->get_width() != real_width)
|| (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;
}
}
source_texture = std::make_shared<gs::texture>(real_width, real_height, GS_RGBA, 1 + mip_levels, nullptr,
gs::texture::flags::BuildMipMaps);
}
mipmapper.rebuild(source_tex, source_texture, generator, generator_strength);
}
// Draw shape to texture
try {
auto op = shape_rt->render(width, height);
if (is_orthographic) {
gs_ortho(-1.0, 1.0, -1.0, 1.0, -farZ, farZ);
} else {
gs_perspective(field_of_view, 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(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"),
enable_mipmapping ? source_texture->get_object() : source_tex->get_object());
gs_draw(GS_TRISTRIP, 0, 4);
}
gs_load_vertexbuffer(nullptr);
} catch (...) {
obs_source_skip_video_filter(source_context);
return;
}
shape_rt->get_texture(shape_tex);
// 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"), shape_tex->get_object());
gs_draw_sprite(shape_tex->get_object(), 0, 0, 0);
}
}