From 9d55013ec26b826c6af9025449d21ffb44ecc83b Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 24 Jan 2019 04:27:04 +0100 Subject: [PATCH] source-mirror: Improve rendering and add new rescaling options Rendering will now use a cached version of the mirrored source in order to reduce rendering cost drastically for heavy sources or scenes. Additionally rescaling now uses the internal scene instead of being a custom implementation, allowing for some new things. Rescaling now has a new option: Bounds Type! This option allows control over just how a source is scaled, by default it is set to Stretch, but there are other options that can keep the aspect ratio of the mirrored source in tact. Additionally the ScalingMethod struct has been replaced with obs_scale_type, which means that Bilinear Low Resolution is now no longer an option. Implements #35 --- data/locale/en-US.ini | 7 ++ source/source-mirror.cpp | 182 +++++++++++++++++---------------------- source/source-mirror.hpp | 39 +++++---- 3 files changed, 105 insertions(+), 123 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index fbd6dfa3..9ed7a1e1 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -196,3 +196,10 @@ Source.Mirror.Scaling.Size="Size" Source.Mirror.Scaling.Size.Description="What size should we rescale to? (WxH format)" Source.Mirror.Scaling.TransformKeepOriginal="Use Original Size for Transforms" Source.Mirror.Scaling.TransformKeepOriginal.Description="Should the filter not modify the size of the source?" +Source.Mirror.Scaling.Bounds="Bounds Type" +Source.Mirror.Scaling.Bounds.Description="How should the source be rescaled?\n- 'Stretch' stretches the source to fill the rescaled region, breaking aspect ratio for the source.\n- 'Fit' scales the source to fit the smallest rectangle that is not smaller than the rescaled region.\n- 'Fill' scales the source to fit the largest rectangle that is not larger than the rescaled region.\n- 'Fill to Width' scales the source to fill the width of the rescaled region.\n- 'Fill to Height' scales the source to fill the height of the rescaled region." +Source.Mirror.Scaling.Bounds.Stretch="Stretch" +Source.Mirror.Scaling.Bounds.Fit="Fit" +Source.Mirror.Scaling.Bounds.Fill="Fill" +Source.Mirror.Scaling.Bounds.FillWidth="Fill to Width" +Source.Mirror.Scaling.Bounds.FillHeight="Fill to Height" diff --git a/source/source-mirror.cpp b/source/source-mirror.cpp index 77c7af23..42eb3b35 100644 --- a/source/source-mirror.cpp +++ b/source/source-mirror.cpp @@ -45,19 +45,16 @@ #define P_SCALING_METHOD "Source.Mirror.Scaling.Method" #define P_SCALING_METHOD_POINT "Source.Mirror.Scaling.Method.Point" #define P_SCALING_METHOD_BILINEAR "Source.Mirror.Scaling.Method.Bilinear" -#define P_SCALING_METHOD_BILINEARLOWRES "Source.Mirror.Scaling.Method.BilinearLowRes" #define P_SCALING_METHOD_BICUBIC "Source.Mirror.Scaling.Method.Bicubic" #define P_SCALING_METHOD_LANCZOS "Source.Mirror.Scaling.Method.Lanczos" #define P_SCALING_SIZE "Source.Mirror.Scaling.Size" #define P_SCALING_TRANSFORMKEEPORIGINAL "Source.Mirror.Scaling.TransformKeepOriginal" - -enum class ScalingMethod : int64_t { - Point, - Bilinear, - BilinearLowRes, - Bicubic, - Lanczos, -}; +#define P_SCALING_BOUNDS "Source.Mirror.Scaling.Bounds" +#define P_SCALING_BOUNDS_STRETCH "Source.Mirror.Scaling.Bounds.Stretch" +#define P_SCALING_BOUNDS_FIT "Source.Mirror.Scaling.Bounds.Fit" +#define P_SCALING_BOUNDS_FILL "Source.Mirror.Scaling.Bounds.Fill" +#define P_SCALING_BOUNDS_FILLWIDTH "Source.Mirror.Scaling.Bounds.FillWidth" +#define P_SCALING_BOUNDS_FILLHEIGHT "Source.Mirror.Scaling.Bounds.FillHeight" // Initializer & Finalizer Source::MirrorAddon* sourceMirrorInstance; @@ -106,7 +103,9 @@ void Source::MirrorAddon::get_defaults(obs_data_t* data) obs_data_set_default_bool(data, P_SOURCE_AUDIO, false); obs_data_set_default_bool(data, P_SCALING, false); obs_data_set_default_string(data, P_SCALING_SIZE, "100x100"); - obs_data_set_default_int(data, P_SCALING_METHOD, (int64_t)ScalingMethod::Bilinear); + obs_data_set_default_int(data, P_SCALING_METHOD, (int64_t)obs_scale_type::OBS_SCALE_BILINEAR); + obs_data_set_default_bool(data, P_SCALING_TRANSFORMKEEPORIGINAL, false); + obs_data_set_default_int(data, P_SCALING_BOUNDS, (int64_t)obs_bounds_type::OBS_BOUNDS_STRETCH); } bool Source::MirrorAddon::modified_properties(obs_properties_t* pr, obs_property_t* p, obs_data_t* data) @@ -128,6 +127,7 @@ bool Source::MirrorAddon::modified_properties(obs_properties_t* pr, obs_property bool show = obs_data_get_bool(data, P_SCALING); obs_property_set_visible(obs_properties_get(pr, P_SCALING_METHOD), show); obs_property_set_visible(obs_properties_get(pr, P_SCALING_SIZE), show); + obs_property_set_visible(obs_properties_get(pr, P_SCALING_BOUNDS), show); return true; } @@ -175,11 +175,10 @@ obs_properties_t* Source::MirrorAddon::get_properties(void*) p = obs_properties_add_list(pr, P_SCALING_METHOD, P_TRANSLATE(P_SCALING_METHOD), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_METHOD))); - obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_POINT), (int64_t)ScalingMethod::Point); - obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEAR), (int64_t)ScalingMethod::Bilinear); - obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEARLOWRES), (int64_t)ScalingMethod::BilinearLowRes); - obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BICUBIC), (int64_t)ScalingMethod::Bicubic); - obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_LANCZOS), (int64_t)ScalingMethod::Lanczos); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_POINT), (int64_t)obs_scale_type::OBS_SCALE_POINT); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BILINEAR), (int64_t)obs_scale_type::OBS_SCALE_BILINEAR); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_BICUBIC), (int64_t)obs_scale_type::OBS_SCALE_BICUBIC); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_METHOD_LANCZOS), (int64_t)obs_scale_type::OBS_SCALE_LANCZOS); p = obs_properties_add_text(pr, P_SCALING_SIZE, P_TRANSLATE(P_SCALING_SIZE), OBS_TEXT_DEFAULT); obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_SIZE))); @@ -187,6 +186,17 @@ obs_properties_t* Source::MirrorAddon::get_properties(void*) p = obs_properties_add_bool(pr, P_SCALING_TRANSFORMKEEPORIGINAL, P_TRANSLATE(P_SCALING_TRANSFORMKEEPORIGINAL)); obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_TRANSFORMKEEPORIGINAL))); + p = obs_properties_add_list(pr, P_SCALING_BOUNDS, P_TRANSLATE(P_SCALING_BOUNDS), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SCALING_BOUNDS))); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_STRETCH), (int64_t)obs_bounds_type::OBS_BOUNDS_STRETCH); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FIT), (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_INNER); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILL), (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_OUTER); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILLWIDTH), + (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_TO_WIDTH); + obs_property_list_add_int(p, P_TRANSLATE(P_SCALING_BOUNDS_FILLHEIGHT), + (int64_t)obs_bounds_type::OBS_BOUNDS_SCALE_TO_HEIGHT); + return pr; } @@ -337,9 +347,10 @@ void Source::Mirror::acquire_input(std::string source_name) } Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) - : m_self(src), m_active(false), m_tick(0), m_rescale_enabled(false), m_rescale_effect(nullptr), - m_rescale_keep_orig_size(false), m_rescale_width(1), m_rescale_height(1), m_audio_enabled(false), m_audio_kill_thread(false), - m_audio_have_output(false), m_source_item(nullptr) + : m_self(src), m_active(true), m_tick(0), m_scene_rendered(false), m_rescale_enabled(false), + m_rescale_keep_orig_size(false), m_rescale_width(1), m_rescale_height(1), + m_rescale_type(obs_scale_type::OBS_SCALE_BICUBIC), m_rescale_bounds(obs_bounds_type::OBS_BOUNDS_STRETCH), + m_audio_enabled(false), m_audio_kill_thread(false), m_audio_have_output(false), m_source_item(nullptr) { // Initialize Video Rendering this->m_scene = @@ -347,11 +358,6 @@ Source::Mirror::Mirror(obs_data_t* data, obs_source_t* src) this->m_scene_texture_renderer = std::make_shared(this->m_scene, std::make_shared(this->m_self, false, false)); - // Initialize Rescaling - this->m_rescale_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); - this->m_sampler = std::make_shared(); - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); - // Initialize Audio Rendering this->m_audio_data.resize(MAX_AUDIO_CHANNELS); for (size_t idx = 0; idx < this->m_audio_data.size(); idx++) { @@ -371,11 +377,6 @@ Source::Mirror::~Mirror() this->m_audio_thread.join(); } - // Finalize Rescaling - this->m_rescale_effect = nullptr; - this->m_sampler.reset(); - this->m_rescale_rt.reset(); - // Finalize Video Rendering this->m_scene_texture_renderer.reset(); this->m_scene.reset(); @@ -448,32 +449,9 @@ void Source::Mirror::update(obs_data_t* data) this->m_rescale_height = 1; } - ScalingMethod scaler = (ScalingMethod)obs_data_get_int(data, P_SCALING_METHOD); - switch (scaler) { - case ScalingMethod::Point: - default: - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); - this->m_sampler->set_filter(GS_FILTER_POINT); - break; - case ScalingMethod::Bilinear: - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); - this->m_sampler->set_filter(GS_FILTER_LINEAR); - break; - case ScalingMethod::BilinearLowRes: - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_BILINEAR_LOWRES); - this->m_sampler->set_filter(GS_FILTER_LINEAR); - break; - case ScalingMethod::Bicubic: - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_BICUBIC); - this->m_sampler->set_filter(GS_FILTER_LINEAR); - break; - case ScalingMethod::Lanczos: - this->m_rescale_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_LANCZOS); - this->m_sampler->set_filter(GS_FILTER_LINEAR); - break; - } - this->m_rescale_keep_orig_size = obs_data_get_bool(data, P_SCALING_TRANSFORMKEEPORIGINAL); + this->m_rescale_type = (obs_scale_type)obs_data_get_int(data, P_SCALING_METHOD); + this->m_rescale_bounds = (obs_bounds_type)obs_data_get_int(data, P_SCALING_BOUNDS); } } @@ -513,6 +491,34 @@ void Source::Mirror::video_tick(float time) this->acquire_input(this->m_source_name.c_str()); } } + + // Update Scene Item Boundaries + if (this->m_source_item) { + obs_transform_info info; + obs_sceneitem_get_info(this->m_source_item, &info); + + info.pos.x = 0; + info.pos.y = 0; + info.rot = 0; + info.scale.x = 1.f; + info.scale.y = 1.f; + info.bounds.x = float_t(this->m_source->width()); + info.bounds.y = float_t(this->m_source->height()); + info.bounds_type = obs_bounds_type::OBS_BOUNDS_STRETCH; + + if (this->m_rescale_enabled) { + info.bounds.x = float_t(this->m_rescale_width); + info.bounds.y = float_t(this->m_rescale_height); + info.bounds_type = this->m_rescale_bounds; + } + obs_sceneitem_set_info(this->m_source_item, &info); + obs_sceneitem_force_update_transform(this->m_source_item); + + obs_sceneitem_set_scale_filter(this->m_source_item, this->m_rescale_enabled ? this->m_rescale_type + : obs_scale_type::OBS_SCALE_POINT); + } + + m_scene_rendered = false; } void Source::Mirror::video_render(gs_effect_t* effect) @@ -522,61 +528,29 @@ void Source::Mirror::video_render(gs_effect_t* effect) return; } - if (this->m_rescale_enabled && this->m_rescale_effect) { - // Get Size of source. - uint32_t sw, sh; - sw = this->m_source->width(); - sh = this->m_source->height(); - - vec2 bounds; - bounds.x = float_t(sw); - bounds.y = float_t(sh); - obs_sceneitem_set_bounds(this->m_source_item, &bounds); - - // Store original Source Texture - std::shared_ptr tex; - try { - tex = this->m_scene_texture_renderer->render(sw, sh); - } catch (...) { - return; + // Only re-render the scene if there was a video_tick, saves GPU cycles. + if (!m_scene_rendered) { + // Override render size if rescaling is enabled. + uint32_t render_width = this->m_source->width(); + uint32_t render_height = this->m_source->height(); + if (m_rescale_enabled) { + render_width = m_rescale_width; + render_height = m_rescale_height; } - gs_eparam_t* scale_param = gs_effect_get_param_by_name(this->m_rescale_effect, "base_dimension_i"); - if (scale_param) { - vec2 base_res_i; - vec2_set(&base_res_i, 1.0f / float_t(sw), 1.0f / float_t(sh)); - gs_effect_set_vec2(scale_param, &base_res_i); - } + m_scene_texture = this->m_scene_texture_renderer->render(render_width, render_height); + m_scene_rendered = true; + } - if (this->m_rescale_keep_orig_size) { - { - auto op = m_rescale_rt->render(this->m_rescale_width, this->m_rescale_height); - gs_ortho(0, float_t(this->m_rescale_width), 0, float_t(this->m_rescale_height), 0, 1); + // Use default effect unless we are provided a different effect. + if (!effect) { + effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + } - vec4 black; - vec4_zero(&black); - gs_clear(GS_CLEAR_COLOR, &black, 0, 0); - - while (gs_effect_loop(this->m_rescale_effect, "Draw")) { - gs_eparam_t* image = gs_effect_get_param_by_name(this->m_rescale_effect, "image"); - gs_effect_set_next_sampler(image, this->m_sampler->get_object()); - obs_source_draw(tex->get_object(), 0, 0, this->m_rescale_width, this->m_rescale_height, false); - } - } - while (gs_effect_loop(obs_get_base_effect(OBS_EFFECT_DEFAULT), "Draw")) { - gs_eparam_t* image = gs_effect_get_param_by_name(obs_get_base_effect(OBS_EFFECT_DEFAULT), "image"); - gs_effect_set_next_sampler(image, this->m_sampler->get_object()); - obs_source_draw(this->m_rescale_rt->get_object(), 0, 0, sw, sh, false); - } - } else { - while (gs_effect_loop(this->m_rescale_effect, "Draw")) { - gs_eparam_t* image = gs_effect_get_param_by_name(this->m_rescale_effect, "image"); - gs_effect_set_next_sampler(image, this->m_sampler->get_object()); - obs_source_draw(tex->get_object(), 0, 0, this->m_rescale_width, this->m_rescale_height, false); - } - } - } else { - obs_source_video_render(this->m_scene_texture_renderer->get_object()); + // Render the cached scene texture. + gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), m_scene_texture->get_object()); + while (gs_effect_loop(effect, "Draw")) { + gs_draw_sprite(m_scene_texture->get_object(), 0, this->get_width(), this->get_height()); } } diff --git a/source/source-mirror.hpp b/source/source-mirror.hpp index af51c9f7..42c986c5 100644 --- a/source/source-mirror.hpp +++ b/source/source-mirror.hpp @@ -77,31 +77,32 @@ namespace Source { // Video Rendering std::shared_ptr m_scene; std::shared_ptr m_scene_texture_renderer; + std::shared_ptr m_scene_texture; + bool m_scene_rendered; // Rescaling - std::shared_ptr m_rescale_rt; - bool m_rescale_enabled; - gs_effect_t* m_rescale_effect; - bool m_rescale_keep_orig_size; - uint32_t m_rescale_width; - uint32_t m_rescale_height; - std::shared_ptr m_sampler; + bool m_rescale_enabled; + uint32_t m_rescale_width; + uint32_t m_rescale_height; + bool m_rescale_keep_orig_size; + obs_scale_type m_rescale_type; + obs_bounds_type m_rescale_bounds; // Audio Rendering - bool m_audio_enabled; - std::mutex m_audio_lock; - std::condition_variable m_audio_notify; - obs_source_audio m_audio_output; - std::vector> m_audio_data; - std::thread m_audio_thread; - bool m_audio_kill_thread; - bool m_audio_have_output; + bool m_audio_enabled; + std::mutex m_audio_lock; + std::condition_variable m_audio_notify; + obs_source_audio m_audio_output; + std::vector> m_audio_data; + std::thread m_audio_thread; + bool m_audio_kill_thread; + bool m_audio_have_output; // Input - std::shared_ptr m_source; - obs_sceneitem_t* m_source_item; - std::string m_source_name; - std::shared_ptr m_source_audio; + std::shared_ptr m_source; + obs_sceneitem_t* m_source_item; + std::string m_source_name; + std::shared_ptr m_source_audio; private: void release_input();