* Added support for sub layers in the image mixer.
* Added blend_mode support to layers in a scene_producer.
{
CASPAR_LOG(info) << L"Initialized Streaming SIMD Extensions Accelerated CPU Image Mixer";
}
-
- void begin_layer(core::blend_mode blend_mode)
- {
- }
void push(const core::frame_transform& transform)
{
{
transform_stack_.pop_back();
}
-
- void end_layer()
- {
- }
std::future<array<const std::uint8_t>> render(const core::video_format_desc& format_desc)
{
void image_mixer::visit(const core::const_frame& frame){impl_->visit(frame);}
void image_mixer::pop(){impl_->pop();}
std::future<array<const std::uint8_t>> image_mixer::operator()(const core::video_format_desc& format_desc){return impl_->render(format_desc);}
-void image_mixer::begin_layer(core::blend_mode blend_mode){impl_->begin_layer(blend_mode);}
-void image_mixer::end_layer(){impl_->end_layer();}
core::mutable_frame image_mixer::create_frame(const void* tag, const core::pixel_format_desc& desc) {return impl_->create_frame(tag, desc);}
}}}
// Methods
- virtual void begin_layer(core::blend_mode blend_mode);
- virtual void end_layer();
-
virtual void push(const core::frame_transform& frame);
virtual void visit(const core::const_frame& frame);
virtual void pop();
struct layer
{
+ std::vector<layer> sublayers;
std::vector<item> items;
core::blend_mode blend_mode;
- layer()
- : blend_mode(core::blend_mode::normal)
- {
- }
-
- layer(std::vector<item> items, core::blend_mode blend_mode)
- : items(std::move(items))
- , blend_mode(blend_mode)
+ layer(core::blend_mode blend_mode)
+ : blend_mode(blend_mode)
{
}
};
std::shared_ptr<texture> layer_key_texture;
for (auto& layer : layers)
+ {
+ draw(target_texture, layer.sublayers, format_desc, field_mode);
draw(target_texture, std::move(layer), layer_key_texture, format_desc, field_mode);
+ }
}
void draw(spl::shared_ptr<texture>& target_texture,
image_renderer renderer_;
std::vector<core::image_transform> transform_stack_;
std::vector<layer> layers_; // layer/stream/items
+ std::vector<layer*> layer_stack_;
public:
impl(const spl::shared_ptr<device>& ogl, bool blend_modes_wanted)
: ogl_(ogl)
{
CASPAR_LOG(info) << L"Initialized OpenGL Accelerated GPU Image Mixer";
}
-
- void begin_layer(core::blend_mode blend_mode)
- {
- layers_.push_back(layer(std::vector<item>(), blend_mode));
- }
void push(const core::frame_transform& transform)
{
- transform_stack_.push_back(transform_stack_.back()*transform.image_transform);
+ auto previous_layer_depth = transform_stack_.back().layer_depth;
+ transform_stack_.push_back(transform_stack_.back() * transform.image_transform);
+ auto new_layer_depth = transform_stack_.back().layer_depth;
+
+ if (previous_layer_depth < new_layer_depth)
+ {
+ layer new_layer(transform_stack_.back().blend_mode);
+
+ if (layer_stack_.empty())
+ {
+ layers_.push_back(std::move(new_layer));
+ layer_stack_.push_back(&layers_.back());
+ }
+ else
+ {
+ layer_stack_.back()->sublayers.push_back(std::move(new_layer));
+ layer_stack_.push_back(&layer_stack_.back()->sublayers.back());
+ }
+ }
+
}
void visit(const core::const_frame& frame)
for(int n = 0; n < static_cast<int>(item.pix_desc.planes.size()); ++n)
item.textures.push_back(ogl_->copy_async(frame.image_data(n), item.pix_desc.planes[n].width, item.pix_desc.planes[n].height, item.pix_desc.planes[n].stride, item.transform.use_mipmap));
- layers_.back().items.push_back(item);
+ layer_stack_.back()->items.push_back(item);
}
void pop()
{
transform_stack_.pop_back();
- }
-
- void end_layer()
- {
+ layer_stack_.resize(transform_stack_.back().layer_depth);
}
std::future<array<const std::uint8_t>> render(const core::video_format_desc& format_desc)
void image_mixer::visit(const core::const_frame& frame){impl_->visit(frame);}
void image_mixer::pop(){impl_->pop();}
std::future<array<const std::uint8_t>> image_mixer::operator()(const core::video_format_desc& format_desc){return impl_->render(format_desc);}
-void image_mixer::begin_layer(core::blend_mode blend_mode){impl_->begin_layer(blend_mode);}
-void image_mixer::end_layer(){impl_->end_layer();}
core::mutable_frame image_mixer::create_frame(const void* tag, const core::pixel_format_desc& desc) {return impl_->create_frame(tag, desc);}
}}}
core::mutable_frame create_frame(const void* tag, const core::pixel_format_desc& desc) override;
// core::image_mixer
-
- void begin_layer(core::blend_mode blend_mode) override;
- void end_layer() override;
void push(const core::frame_transform& frame) override;
void visit(const core::const_frame& frame) override;
draw_frame draw_frame::still(draw_frame frame)
{
- frame.transform().image_transform.is_still = true;
- frame.transform().audio_transform.is_still = true;
+ frame.transform().image_transform.is_still = true;
+ frame.transform().audio_transform.is_still = true;
return frame;
}
is_mix |= other.is_mix;
is_still |= other.is_still;
use_mipmap |= other.use_mipmap;
+ blend_mode = std::max(blend_mode, other.blend_mode);
+ layer_depth += other.layer_depth;
return *this;
}
result.is_mix = source.is_mix | dest.is_mix;
result.is_still = source.is_still | dest.is_still;
result.use_mipmap = source.use_mipmap | dest.use_mipmap;
+ result.blend_mode = std::max(source.blend_mode, dest.blend_mode);
+ result.layer_depth = dest.layer_depth;
do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
lhs.is_mix == rhs.is_mix &&
lhs.is_still == rhs.is_still &&
lhs.use_mipmap == rhs.use_mipmap &&
+ lhs.blend_mode == rhs.blend_mode &&
+ lhs.layer_depth == rhs.layer_depth &&
lhs.crop == rhs.crop &&
lhs.perspective == rhs.perspective;
}
#include <common/tweener.h>
#include <core/video_format.h>
+#include <core/mixer/image/blend_modes.h>
#include <boost/array.hpp>
bool is_mix = false;
bool is_still = false;
bool use_mipmap = false;
+ core::blend_mode blend_mode = core::blend_mode::normal;
+ int layer_depth = 0;
image_transform& operator*=(const image_transform &other);
image_transform operator*(const image_transform &other) const;
virtual void push(const struct frame_transform& frame) = 0;
virtual void visit(const class const_frame& frame) = 0;
virtual void pop() = 0;
-
- virtual void begin_layer(blend_mode blend_mode) = 0;
- virtual void end_layer() = 0;
virtual std::future<array<const std::uint8_t>> operator()(const struct video_format_desc& format_desc) = 0;
spl::shared_ptr<diagnostics::graph> graph_;
audio_mixer audio_mixer_;
spl::shared_ptr<image_mixer> image_mixer_;
-
- std::unordered_map<int, blend_mode> blend_modes_;
executor executor_ { L"mixer" };
for (auto& frame : frames)
{
- auto blend_it = blend_modes_.find(frame.first);
- image_mixer_->begin_layer(blend_it != blend_modes_.end() ? blend_it->second : blend_mode::normal);
-
- frame.second.accept(audio_mixer_);
+ frame.second.accept(audio_mixer_);
+ frame.second.transform().image_transform.layer_depth = 1;
frame.second.accept(*image_mixer_);
-
- image_mixer_->end_layer();
}
auto image = (*image_mixer_)(format_desc);
return frame;
}
-
- void set_blend_mode(int index, blend_mode value)
- {
- executor_.begin_invoke([=]
- {
- auto it = blend_modes_.find(index);
- if(it == blend_modes_.end())
- blend_modes_.insert(std::make_pair(index, value));
- else
- it->second = value;
- }, task_priority::high_priority);
- }
-
- blend_mode get_blend_mode(int index)
- {
- return executor_.invoke([=]
- {
- return blend_modes_[index];
- }, task_priority::high_priority);
- }
-
- void clear_blend_mode(int index)
- {
- executor_.begin_invoke([=]
- {
- blend_modes_.erase(index);
- }, task_priority::high_priority);
- }
-
- void clear_blend_modes()
- {
- executor_.begin_invoke([=]
- {
- blend_modes_.clear();
- }, task_priority::high_priority);
- }
void set_master_volume(float volume)
{
mixer::mixer(spl::shared_ptr<diagnostics::graph> graph, spl::shared_ptr<image_mixer> image_mixer)
: impl_(new impl(std::move(graph), std::move(image_mixer))){}
-void mixer::set_blend_mode(int index, blend_mode value){impl_->set_blend_mode(index, value);}
-blend_mode mixer::get_blend_mode(int index) { return impl_->get_blend_mode(index); }
-void mixer::clear_blend_mode(int index) { impl_->clear_blend_mode(index); }
-void mixer::clear_blend_modes() { impl_->clear_blend_modes(); }
void mixer::set_master_volume(float volume) { impl_->set_master_volume(volume); }
float mixer::get_master_volume() { return impl_->get_master_volume(); }
std::future<boost::property_tree::wptree> mixer::info() const{return impl_->info();}
// Methods
class const_frame operator()(std::map<int, class draw_frame> frames, const struct video_format_desc& format_desc);
-
- void set_blend_mode(int index, blend_mode value);
- blend_mode get_blend_mode(int index);
- void clear_blend_mode(int index);
- void clear_blend_modes();
void set_master_volume(float volume);
float get_master_volume();
}).detach();
}
- draw_frame receive() override {return producer_->receive();}
+ draw_frame receive() override {return producer_->receive();}
std::wstring print() const override {return producer_->print();}
void paused(bool value) override {producer_->paused(value);}
std::wstring name() const override {return producer_->name();}
transform.image_transform.opacity = layer.adjustments.opacity.get();
transform.image_transform.is_key = layer.is_key.get();
transform.image_transform.use_mipmap = layer.use_mipmap.get();
+ transform.image_transform.blend_mode = layer.blend_mode.get();
+
+ // Mark as sublayer, so it will be composited separately by the mixer.
+ transform.image_transform.layer_depth = 1;
return transform;
}
binding<bool> hidden;
binding<bool> is_key;
binding<bool> use_mipmap;
+ binding<core::blend_mode> blend_mode;
explicit layer(const std::wstring& name, const spl::shared_ptr<frame_producer>& producer);
};
layer.adjustments.opacity = scene->create_variable<double>(variable_prefix + L"adjustment.opacity", false, elem.second.get(L"adjustments.opacity", L"1.0"));
layer.is_key = scene->create_variable<bool>(variable_prefix + L"is_key", false, elem.second.get(L"is_key", L"false"));
layer.use_mipmap = scene->create_variable<bool>(variable_prefix + L"use_mipmap", false, elem.second.get(L"use_mipmap", L"false"));
+ layer.blend_mode = scene->create_variable<std::wstring>(variable_prefix + L"blend_mode", false, elem.second.get(L"blend_mode", L"normal")).transformed([](const std::wstring& b) { return get_blend_mode(b); });
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
else if(boost::iequals(parameters()[0], L"BLEND"))
{
if (parameters().size() == 1)
- {
- auto blend_mode = channel()->mixer().get_blend_mode(layer_index());
- SetReplyString(L"201 MIXER OK\r\n"
- + boost::lexical_cast<std::wstring>(get_blend_mode(blend_mode))
- + L"\r\n");
- return true;
- }
+ return reply_value([](const frame_transform& t) { return get_blend_mode(t.image_transform.blend_mode); });
- auto blend_str = parameters().at(1);
- int layer = layer_index();
- channel()->mixer().set_blend_mode(layer, get_blend_mode(blend_str));
+ auto value = get_blend_mode(parameters().at(1));
+ transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+ {
+ transform.image_transform.blend_mode = value;
+ return transform;
+ }, 0, L"linear"));
}
else if(boost::iequals(parameters()[0], L"MASTERVOLUME"))
{
if (layer == std::numeric_limits<int>::max())
{
channel()->stage().clear_transforms();
- channel()->mixer().clear_blend_modes();
}
else
{
channel()->stage().clear_transforms(layer);
- channel()->mixer().clear_blend_mode(layer);
}
}
else if(boost::iequals(parameters()[0], L"COMMIT"))