diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ec3d88..cc0aec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,6 @@ set(VMIX_SRCS Shader.cpp ImageShader.cpp ImageProcessingShader.cpp - ImageFilter.cpp UpdateCallback.cpp Scene.cpp Primitives.cpp @@ -426,6 +425,9 @@ set(VMIX_SRCS NetworkSource.cpp MultiFileSource.cpp FrameBuffer.cpp + FrameBufferFilter.cpp + ImageFilter.cpp + DelayFilter.cpp RenderingManager.cpp UserInterfaceManager.cpp PickingVisitor.cpp diff --git a/CloneSource.cpp b/CloneSource.cpp index 29833b6..da6fbed 100644 --- a/CloneSource.cpp +++ b/CloneSource.cpp @@ -25,15 +25,16 @@ #include "Log.h" #include "defines.h" #include "Resource.h" +#include "Decorations.h" #include "Visitor.h" #include "FrameBuffer.h" -#include "Decorations.h" +#include "FrameBufferFilter.h" +#include "DelayFilter.h" +#include "ImageFilter.h" #include "CloneSource.h" - -CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin), cloningsurface_(nullptr), - garbage_image_(nullptr), timer_reset_(false), delay_(0.0), paused_(false), filter_render_(nullptr) +CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin), paused_(false), filter_(nullptr) { // initial name copies the origin name: diplucates are namanged in session name_ = origin->name(); @@ -48,8 +49,8 @@ CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(orig connection_->target = origin->groups_[View::MIXING]->translation_; groups_[View::MIXING]->attach(connection_); - // init timer - timer_ = g_timer_new (); + // default to pass-through filter + filter_ = new PassthroughFilter; } CloneSource::~CloneSource() @@ -57,51 +58,22 @@ CloneSource::~CloneSource() if (origin_) origin_->clones_.remove(this); - // delete all frame buffers - while (!images_.empty()) { - if (images_.front() != nullptr) - delete images_.front(); - images_.pop(); - } - - if (filter_render_) - delete filter_render_; - - if (cloningsurface_) - delete cloningsurface_; - - g_free(timer_); + delete filter_; } void CloneSource::init() { if (origin_ && origin_->ready_ && origin_->mode_ > Source::UNINITIALIZED && origin_->renderbuffer_) { - // frame buffers where to draw frames from the origin source - glm::vec3 res = origin_->frame()->resolution(); - images_.push( new FrameBuffer( res, origin_->frame()->use_alpha() ) ); - origin_->frame()->blit( images_.front() ); - timestamps_.push( origin_->playtime() ); - elapsed_.push( 0. ); - // create render Frame buffer matching size of images - FrameBuffer *renderbuffer = new FrameBuffer( res, true ); + FrameBuffer *renderbuffer = new FrameBuffer( origin_->frame()->resolution(), origin_->frame()->use_alpha() ); // set the renderbuffer of the source and attach rendering nodes attach(renderbuffer); - // create filter for this resolution (with alpha channel) - filter_render_ = new ImageFilterRenderer( res ); - - // provide initial texture to filter - filter_render_->setInputTexture( images_.front()->texture() ); - // force update of activation mode active_ = true; - // ask to reset elapsed-timer - timer_reset_ = true; - // deep update to reorder ++View::need_deep_update_; @@ -117,10 +89,10 @@ void CloneSource::render() else { // render filter image - filter_render_->draw(); + filter_->draw( origin_->frame() ); // ensure correct output texture is displayed (could have changed if filter changed) - texturesurface_->setTextureIndex( filter_render_->getOutputTexture() ); + texturesurface_->setTextureIndex( filter_->texture() ); // render textured surface into frame buffer renderbuffer_->begin(); @@ -130,19 +102,19 @@ void CloneSource::render() } } - void CloneSource::setActive (bool on) { // try to activate (may fail if source is cloned) Source::setActive(on); if (origin_) { - if ( mode_ > Source::UNINITIALIZED) + + if ( mode_ > Source::UNINITIALIZED ) origin_->touch(); // change visibility of active surface (show preview of origin when inactive) if (activesurface_) { - if (active_ || images_.empty()) + if (active_) activesurface_->setTextureIndex(Resource::getTextureTransparent()); else activesurface_->setTextureIndex(renderbuffer_->texture()); @@ -154,70 +126,10 @@ void CloneSource::update(float dt) { Source::update(dt); - if (origin_ && !images_.empty()) { + if (origin_) { if (!paused_ && active_) - { - filter_render_->update(dt); - - // Reset elapsed timer on request (init or replay) - if ( timer_reset_ ) { - g_timer_start(timer_); - timer_reset_ = false; - } - // What time is it? - double now = g_timer_elapsed (timer_, NULL); - - // is the total buffer of images longer than delay ? - if ( !images_.empty() && now - elapsed_.front() > delay_ ) - { - // if temporary FBO was pending to be deleted, delete it now - if (garbage_image_ != nullptr) { - delete garbage_image_; - garbage_image_ = nullptr; - } - - // remember FBO to be reused if needed (see below) or deleted later - garbage_image_ = images_.front(); - - // remove element from queue (front) - images_.pop(); - elapsed_.pop(); - timestamps_.pop(); - } - - // add image to queue to accumulate buffer images until delay reached (with margin) - if ( images_.empty() || now - elapsed_.front() < delay_ + (dt * 0.002) ) - { - // create a FBO if none can be reused (from above) and test for RAM in GPU - if (garbage_image_ == nullptr && ( images_.empty() || Rendering::shouldHaveEnoughMemory(origin_->frame()->resolution(), origin_->frame()->use_alpha()) ) ){ - garbage_image_ = new FrameBuffer( origin_->frame()->resolution(), origin_->frame()->use_alpha() ); - } - // image available - if (garbage_image_ != nullptr) { - // add element to queue (back) - images_.push( garbage_image_ ); - elapsed_.push( now ); - timestamps_.push( origin_->playtime() ); - // garbage_image_ FBO is now used, it should not be deleted - garbage_image_ = nullptr; - } - else { - // set delay to maximum affordable - delay_ = now - elapsed_.front() - (dt * 0.002); - Log::Warning("Cannot satisfy delay for Clone %s: not enough RAM in graphics card.", name_.c_str()); - } - } - - // make sure the queue is not empty (in case of failure above) - if ( !images_.empty() ) { - // blit origin framebuffer in the newest image (back) - origin_->frame()->blit( images_.back() ); - - // update the surface to be rendered with the oldest image (front) - filter_render_->setInputTexture( images_.front()->texture() ); - } - } + filter_->update(dt); // update connection line target to position of origin source connection_->target = glm::inverse( GlmToolkit::transform(groups_[View::MIXING]->translation_, glm::vec3(0), groups_[View::MIXING]->scale_) ) * @@ -225,26 +137,28 @@ void CloneSource::update(float dt) } } - -void CloneSource::setDelay(double second) +void CloneSource::setFilter(FrameBufferFilter::Type T) { - delay_ = CLAMP(second, 0.0, 2.0); -} + if (filter_) + delete filter_; + + switch (T) + { + case FrameBufferFilter::FILTER_DELAY: + filter_ = new DelayFilter; + break; + case FrameBufferFilter::FILTER_IMAGE: + filter_ = new ImageFilter; + break; + default: + case FrameBufferFilter::FILTER_PASSTHROUGH: + filter_ = new PassthroughFilter; + break; + } -void CloneSource::setFilter(const ImageFilter &filter, std::promise *ret) -{ - if (filter_render_) - filter_render_->setFilter(filter, ret); -} + // TODO : resampling of renderbuffer -ImageFilter CloneSource::filter() const -{ - if (filter_render_) - return filter_render_->filter(); - - ImageFilter f; - return f; } void CloneSource::play (bool on) @@ -252,9 +166,13 @@ void CloneSource::play (bool on) // if a different state is asked if (paused_ == on) { + // play / pause filter to suspend clone + filter_->setEnabled( on ); + // restart clean if was paused if (paused_) replay(); + // toggle state paused_ = !on; } @@ -262,48 +180,26 @@ void CloneSource::play (bool on) bool CloneSource::playable () const { - if (filter_render_->enabled()) + if (filter_ && filter_->enabled()) return true; + if (origin_) return origin_->playable(); - return true; + + return false; } void CloneSource::replay() { - // clear to_delete_ FBO if pending - if (garbage_image_ != nullptr) { - delete garbage_image_; - garbage_image_ = nullptr; - } + // TODO: add reset to Filter - // remove all images except the one in the back (newest) - while (images_.size() > 1) { - // do not delete immediately the (oldest) front image (the FBO is currently displayed) - if (garbage_image_ == nullptr) - garbage_image_ = images_.front(); - // delete other FBO (unused) - else if (images_.front() != nullptr) - delete images_.front(); - images_.pop(); - } - - // remove all timing - while (!elapsed_.empty()) - elapsed_.pop(); - // reset elapsed timer to 0 - timer_reset_ = true; - elapsed_.push(0.); - - // remove all timestamps - while (!timestamps_.empty()) - timestamps_.pop(); - timestamps_.push(0); } guint64 CloneSource::playtime () const { - return timestamps_.front(); + // TODO : get time of ImageFilter? Get Delay ? + + return origin_->playtime(); } diff --git a/CloneSource.h b/CloneSource.h index a223409..df2cd57 100644 --- a/CloneSource.h +++ b/CloneSource.h @@ -4,9 +4,9 @@ #include #include "Source.h" -#include "ImageFilter.h" +#include "FrameBufferFilter.h" -class ImageFilterRenderer; +#include "ImageFilter.h" // TODO REMOVE set_filtering_program class CloneSource : public Source { @@ -27,20 +27,17 @@ public: bool failed() const override { return origin_ == nullptr; } void accept (Visitor& v) override; void render() override; + glm::ivec2 icon() const override; + std::string info() const override; // implementation of cloning mechanism inline void detach() { origin_ = nullptr; } inline Source *origin() const { return origin_; } - // Clone properties - void setDelay(double second); - inline double delay() const { return delay_; } + // Filtering + void setFilter(FrameBufferFilter::Type T); + inline FrameBufferFilter *filter() { return filter_; } - void setFilter(const ImageFilter &filter, std::promise *ret = nullptr); - ImageFilter filter() const; - - glm::ivec2 icon() const override; - std::string info() const override; protected: // only Source class can create new CloneSource via clone(); @@ -49,26 +46,15 @@ protected: void init() override; Source *origin_; - // cloning & queue of past frames - std::queue images_; - Surface *cloningsurface_; - FrameBuffer *garbage_image_; - - // time management - GTimer *timer_; - bool timer_reset_; - std::queue elapsed_; - std::queue timestamps_; - double delay_; - // control bool paused_; - // filter - ImageFilterRenderer *filter_render_; - // connecting line class DotLine *connection_; + + // Filter + FrameBufferFilter *filter_; + }; diff --git a/DelayFilter.cpp b/DelayFilter.cpp index e69de29..86b8c2b 100644 --- a/DelayFilter.cpp +++ b/DelayFilter.cpp @@ -0,0 +1,114 @@ + +#include "Log.h" +#include "FrameBuffer.h" +#include "Resource.h" +#include "Primitives.h" +#include "Visitor.h" + +#include "DelayFilter.h" + +DelayFilter::DelayFilter(): FrameBufferFilter(), + temp_frame_(nullptr), now_(0.0), delay_(0.5) +{ + +} + +DelayFilter::~DelayFilter() +{ + // delete all frame buffers + while (!frames_.empty()) { + if (frames_.front() != nullptr) + delete frames_.front(); + frames_.pop(); + } + + while (!elapsed_.empty()) + elapsed_.pop(); +} + +void DelayFilter::update (float dt) +{ + if (input_) { + + // What time is it? + now_ += double(dt) * 0.001; + + // if temporary FBO was pending to be deleted, delete it now + if (temp_frame_ != nullptr) { + delete temp_frame_; + temp_frame_ = nullptr; + } + + // is the total buffer of images longer than delay ? + if ( !frames_.empty() && now_ - elapsed_.front() > delay_ ) + { + // remember FBO to be reused if needed (see below) or deleted later + temp_frame_ = frames_.front(); + + // remove element from queue (front) + frames_.pop(); + elapsed_.pop(); + } + + // add image to queue to accumulate buffer images until delay reached (with margin) + if ( frames_.empty() || now_ - elapsed_.front() < delay_ + ( double(dt) * 0.002) ) + { + // create a FBO if none can be reused (from above) and test for RAM in GPU + if (temp_frame_ == nullptr && ( frames_.empty() || Rendering::shouldHaveEnoughMemory(input_->resolution(), input_->use_alpha()) ) ){ + temp_frame_ = new FrameBuffer( input_->resolution(), input_->use_alpha() ); + } + // image available + if (temp_frame_ != nullptr) { + // add element to queue (back) + frames_.push( temp_frame_ ); + elapsed_.push( now_ ); + // temp_frame_ FBO is now used, it should not be deleted + temp_frame_ = nullptr; + } + else { + // set delay to maximum affordable + delay_ = now_ - elapsed_.front() - (dt * 0.001); + Log::Warning("Cannot satisfy delay: not enough RAM in graphics card."); + } + } + } +} + +uint DelayFilter::texture () const +{ + if (!frames_.empty()) + return frames_.front()->texture(); + else if (input_) + return input_->texture(); + else + return Resource::getTextureBlack(); +} + +glm::vec3 DelayFilter::resolution () const +{ + if (input_) + return input_->resolution(); + + return glm::vec3(1,1,0); +} + +void DelayFilter::draw (FrameBuffer *input) +{ + input_ = input; + + if ( enabled() ) // TODO TEST DISABLE + { + // make sure the queue is not empty + if ( input_ && !frames_.empty() ) { + // blit input framebuffer in the newest image in queue (back) + input_->blit( frames_.back() ); + } + } +} + +void DelayFilter::accept (Visitor& v) +{ + FrameBufferFilter::accept(v); + v.visit(*this); +} + diff --git a/DelayFilter.h b/DelayFilter.h index e69de29..dfc21a7 100644 --- a/DelayFilter.h +++ b/DelayFilter.h @@ -0,0 +1,43 @@ +#ifndef DELAYFILTER_H +#define DELAYFILTER_H + +#include +#include + +#include "FrameBufferFilter.h" + +class Surface; +class FrameBuffer; + +class DelayFilter : public FrameBufferFilter +{ +public: + DelayFilter(); + ~DelayFilter(); + + // delay property + inline void setDelay(double second) { delay_ = second; } + inline double delay() const { return delay_; } + + // implementation of FrameBufferFilter + Type type() const { return FrameBufferFilter::FILTER_DELAY; } + uint texture () const override; + glm::vec3 resolution () const override; + void update (float dt) override; + void draw (FrameBuffer *input) override; + void accept (Visitor& v) override; + +private: + // queue of frames + std::queue frames_; + std::queue elapsed_; + + // render management + FrameBuffer *temp_frame_; + + // time management + double now_; + double delay_; +}; + +#endif // DELAYFILTER_H diff --git a/FrameBufferFilter.cpp b/FrameBufferFilter.cpp index e69de29..d6c264f 100644 --- a/FrameBufferFilter.cpp +++ b/FrameBufferFilter.cpp @@ -0,0 +1,47 @@ +#include "FrameBuffer.h" +#include "Resource.h" +#include "Visitor.h" + +#include "FrameBufferFilter.h" +#include "FrameBufferFilter.h" + +const char* FrameBufferFilter::type_label[FrameBufferFilter::FILTER_INVALID] = { + "None", "Delay", "Custom" +}; + +FrameBufferFilter::FrameBufferFilter() : enabled_(true), input_(nullptr) +{ + +} + +void FrameBufferFilter::draw (FrameBuffer *input) +{ + input_ = input; +} + +void FrameBufferFilter::accept(Visitor& v) +{ + if (input_) + v.visit(*this); +} + +PassthroughFilter::PassthroughFilter() : FrameBufferFilter() +{ + +} + +uint PassthroughFilter::texture() const +{ + if (input_) + return input_->texture(); + else + return Resource::getTextureBlack(); +} + +glm::vec3 PassthroughFilter::resolution() const +{ + if (input_) + return input_->resolution(); + else + return glm::vec3(1,1,0); +} diff --git a/FrameBufferFilter.h b/FrameBufferFilter.h index e69de29..037a1cf 100644 --- a/FrameBufferFilter.h +++ b/FrameBufferFilter.h @@ -0,0 +1,65 @@ +#ifndef FRAMEBUFFERFILTER_H +#define FRAMEBUFFERFILTER_H + +#include + +class Visitor; +class FrameBuffer; + +class FrameBufferFilter +{ + bool enabled_; + +public: + + FrameBufferFilter(); + virtual ~FrameBufferFilter() {} + + // types of sub-classes of filters + typedef enum { + FILTER_PASSTHROUGH = 0, + FILTER_DELAY, + FILTER_IMAGE, + FILTER_INVALID + } Type; + static const char* type_label[FILTER_INVALID]; + virtual Type type () const = 0; + + // get the texture id of the rendered filtered framebuffer + // when not enabled, getOutputTexture should return InputTexture + virtual uint texture () const = 0; + + // get the resolution of the rendered filtered framebuffer + virtual glm::vec3 resolution () const = 0; + + // perform update (non rendering) + virtual void update (float dt) {} + + // draw the input framebuffer and apply the filter + virtual void draw (FrameBuffer *input); + + // visitor for UI and XML + virtual void accept (Visitor& v); + + // when enabled, draw is effective + inline void setEnabled (bool on) { enabled_ = on; } + inline bool enabled () const { return enabled_; } + +protected: + FrameBuffer *input_; +}; + +class PassthroughFilter : public FrameBufferFilter +{ +public: + + PassthroughFilter(); + + // implementation of FrameBufferFilter + Type type() const { return FrameBufferFilter::FILTER_PASSTHROUGH; } + uint texture() const override; + glm::vec3 resolution() const override; +}; + + +#endif // FRAMEBUFFERFILTER_H diff --git a/ImGuiVisitor.cpp b/ImGuiVisitor.cpp index 04e5d0a..bbd9844 100644 --- a/ImGuiVisitor.cpp +++ b/ImGuiVisitor.cpp @@ -43,6 +43,9 @@ #include "MediaPlayer.h" #include "MediaSource.h" #include "CloneSource.h" +#include "FrameBufferFilter.h" +#include "DelayFilter.h" +#include "ImageFilter.h" #include "RenderSource.h" #include "SessionSource.h" #include "PatternSource.h" @@ -741,6 +744,83 @@ void ImGuiVisitor::visit (RenderSource& s) } +void ImGuiVisitor::visit (FrameBufferFilter&) +{ + +} + +void ImGuiVisitor::visit (PassthroughFilter&) +{ + +} + +void ImGuiVisitor::visit (DelayFilter& f) +{ + if (ImGuiToolkit::IconButton(10, 15)) { + f.setDelay(0.f); + Action::manager().store("Delay None"); + } + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + float d = f.delay(); + if (ImGui::SliderFloat("Delay", &d, 0.f, 2.f, d < 0.01f ? "None" : "%.2f s")) + f.setDelay(d); + if (ImGui::IsItemDeactivatedAfterEdit()) { + std::ostringstream oss; + oss << "Delay " << std::setprecision(3) << d << " s"; + Action::manager().store(oss.str()); + } +} + +void ImGuiVisitor::visit (ImageFilter& f) +{ + // TODO : custom filter proposes to open editor + + if (ImGuiToolkit::IconButton(1, 7)) { + + } + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::BeginCombo("##Filters", f.program().name().c_str()) ) + { + for (auto p = FilteringProgram::presets.begin(); p != FilteringProgram::presets.end(); ++p){ + if (ImGui::Selectable( p->name().c_str() )) { + // apply the selected filter to the source + f.setProgram( *p ); + // ask code editor to refresh + // TODO + + + // TODO UNDO std::ostringstream oss; + // oss << s.name() << ": Pattern " << Pattern::get(p).label; + // Action::manager().store(oss.str()); + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text("Preset"); + + std::map filter_parameters = f.program().parameters(); + FilteringProgram target = f.program(); + for (auto param = filter_parameters.begin(); param != filter_parameters.end(); ++param) + { + float v = param->second; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::SliderFloat( param->first.c_str(), &v, 0.f, 1.f, "%.2f")) { + target.setParameter(param->first, v); + f.setProgram( target ); + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + // TODO UNDO + // std::ostringstream oss; + // oss << "Delay " << std::setprecision(3) << d << " s"; + // Action::manager().store(oss.str()); + } + + } +} + void ImGuiVisitor::visit (CloneSource& s) { // info @@ -757,67 +837,32 @@ void ImGuiVisitor::visit (CloneSource& s) UserInterface::manager().showSourceEditor(&s); ImGui::SetCursorPos(pos); + // link to origin source std::string label = std::string(s.origin()->initials()) + " - " + s.origin()->name(); - if ( ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) + if (ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) )) Mixer::manager().setCurrentSource(s.origin()); ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::Text("Origin"); - if (ImGuiToolkit::IconButton(10, 15)) { - s.setDelay(0.f); - Action::manager().store("Delay None"); + // filter selection + std::ostringstream oss; + oss << s.name(); + if (ImGuiToolkit::IconButton(1, 7)) { + s.setFilter( FrameBufferFilter::FILTER_PASSTHROUGH ); + oss << ": Filter None"; + Action::manager().store(oss.str()); } ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - float d = s.delay(); - if (ImGui::SliderFloat("Delay", &d, 0.f, 2.f, d < 0.01f ? "None" : "%.2f s")) - s.setDelay(d); - if (ImGui::IsItemDeactivatedAfterEdit()) { - std::ostringstream oss; - oss << "Delay " << std::setprecision(3) << d << " s"; + int type = (int) s.filter()->type(); + if (ImGui::Combo("Filter", &type, FrameBufferFilter::type_label, IM_ARRAYSIZE(FrameBufferFilter::type_label) )) { + s.setFilter( FrameBufferFilter::Type(type) ); + oss << ": Filter " << FrameBufferFilter::type_label[type]; Action::manager().store(oss.str()); } - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##Filters", s.filter().name().c_str()) ) - { - for (auto f = ImageFilter::presets.begin(); f != ImageFilter::presets.end(); ++f){ - if (ImGui::Selectable( f->name().c_str() )) { - // apply the selected filter to the source - s.setFilter( *f ); - // ask code editor to refresh - UserInterface::manager().showSourceEditor(&s); - - // TODO UNDO std::ostringstream oss; - // oss << s.name() << ": Pattern " << Pattern::get(p).label; - // Action::manager().store(oss.str()); - } - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - ImGui::Text("Filter"); - - std::map filter_parameters = s.filter().parameters(); - ImageFilter target = s.filter(); - for (auto param = filter_parameters.begin(); param != filter_parameters.end(); ++param) - { - // TODO VISIT ImageFilter - float f = param->second; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::SliderFloat( param->first.c_str(), &f, 0.f, 1.f, "%.2f")) { - target.setParameter(param->first, f); - s.setFilter( target ); - } - if (ImGui::IsItemDeactivatedAfterEdit()) { - // TODO UNDO - // std::ostringstream oss; - // oss << "Delay " << std::setprecision(3) << d << " s"; - // Action::manager().store(oss.str()); - } - - } - + // filter options + s.filter()->accept(*this); } diff --git a/ImGuiVisitor.h b/ImGuiVisitor.h index e6dc429..3f94481 100644 --- a/ImGuiVisitor.h +++ b/ImGuiVisitor.h @@ -28,13 +28,18 @@ public: void visit (SessionFileSource& s) override; void visit (SessionGroupSource& s) override; void visit (RenderSource& s) override; - void visit (CloneSource& s) override; void visit (PatternSource& s) override; void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; void visit (MultiFileSource& s) override; void visit (GenericStreamSource& s) override; void visit (SrtReceiverSource& s) override; + + void visit (CloneSource& s) override; + void visit (FrameBufferFilter&) override; + void visit (PassthroughFilter&) override; + void visit (DelayFilter&) override; + void visit (ImageFilter&) override; }; #endif // IMGUIVISITOR_H diff --git a/ImageFilter.cpp b/ImageFilter.cpp index ded76d2..1ca1509 100644 --- a/ImageFilter.cpp +++ b/ImageFilter.cpp @@ -18,7 +18,7 @@ **/ #include -#include +#include #include #include @@ -27,6 +27,7 @@ #include "ImageShader.h" #include "Visitor.h" #include "FrameBuffer.h" +#include "ImageShader.h" #include "Primitives.h" #include "Log.h" @@ -75,40 +76,41 @@ std::string fragmentFooter = "void main() {\n" // "} \n"; -std::list< ImageFilter > ImageFilter::presets = { - ImageFilter(), - ImageFilter("Blur", "shaders/filters/blur_1.glsl", "shaders/filters/blur_2.glsl", { { "Amount", 0.5} }), - ImageFilter("HashBlur", "shaders/filters/hashedblur.glsl", "", { { "Radius", 0.5}, { "Iterations", 0.5 } }), - ImageFilter("Unfocus", "shaders/filters/focus.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Smooth", "shaders/filters/bilinear.glsl", "", { }), - ImageFilter("Denoise", "shaders/filters/denoise.glsl", "", { { "Threshold", 0.5} }), - ImageFilter("Noise", "shaders/filters/noise.glsl", "", { { "Amount", 0.25} }), - ImageFilter("Grain", "shaders/filters/grain.glsl", "", { { "Amount", 0.5} }), - ImageFilter("Bloom", "shaders/filters/bloom.glsl", "", { { "Intensity", 0.5} }), - ImageFilter("Bokeh", "shaders/filters/bokeh.glsl", "", { { "Radius", 1.0} }), - ImageFilter("Kuwahara", "shaders/filters/kuwahara.glsl", "", { { "Radius", 1.0} }), - ImageFilter("Chalk", "shaders/filters/talk.glsl", "", { { "Factor", 1.0} }), - ImageFilter("Sharpen", "shaders/filters/sharp.glsl", "", { { "Amount", 0.5} }), - ImageFilter("Unsharp Mask", "shaders/filters/sharpen_1.glsl", "shaders/filters/sharpen_2.glsl", { { "Amount", 0.5} }), - ImageFilter("Sharp Edge", "shaders/filters/bilinear.glsl", "shaders/filters/sharpenedge.glsl", { { "Strength", 0.5} }), - ImageFilter("Edge", "shaders/filters/bilinear.glsl", "shaders/filters/edge.glsl", { { "Threshold", 0.5} }), - ImageFilter("Sobel", "shaders/filters/sobel.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Freichen", "shaders/filters/freichen.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Stippling","shaders/filters/stippling.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Dithering","shaders/filters/dithering.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Pixelate", "shaders/filters/pixelate.glsl", "", { { "Size", 0.5}, { "Sharpen", 0.5} }), - ImageFilter("Chromakey","shaders/filters/chromakey.glsl", "", { { "Red", 0.05}, { "Green", 0.63}, { "Blue", 0.14}, { "Tolerance", 0.54} }), - ImageFilter("Fisheye", "shaders/filters/fisheye.glsl", "", { { "Factor", 0.5} }), - ImageFilter("Openning", "shaders/filters/erosion.glsl", "shaders/filters/dilation.glsl", { { "Radius", 0.5} }), - ImageFilter("Closing", "shaders/filters/dilation.glsl", "shaders/filters/erosion.glsl", { { "Radius", 0.5} }), - ImageFilter("TopHat", "shaders/filters/erosion.glsl", "shaders/filters/tophat.glsl", { { "Radius", 0.5} }), - ImageFilter("BlackHat", "shaders/filters/dilation.glsl", "shaders/filters/blackhat.glsl", { { "Radius", 0.5} }) - +std::list< FilteringProgram > FilteringProgram::presets = { + FilteringProgram(), + FilteringProgram("Blur", "shaders/filters/blur_1.glsl", "shaders/filters/blur_2.glsl", { { "Amount", 0.5} }), + FilteringProgram("HashBlur", "shaders/filters/hashedblur.glsl", "", { { "Radius", 0.5}, { "Iterations", 0.5 } }), + FilteringProgram("Unfocus", "shaders/filters/focus.glsl", "", { { "Factor", 0.5} }), + FilteringProgram("Smooth", "shaders/filters/bilinear.glsl", "", { }), + FilteringProgram("Kuwahara", "shaders/filters/kuwahara.glsl", "", { { "Radius", 1.0} }), + FilteringProgram("Denoise", "shaders/filters/denoise.glsl", "", { { "Threshold", 0.5} }), + FilteringProgram("Noise", "shaders/filters/noise.glsl", "", { { "Amount", 0.25} }), + FilteringProgram("Grain", "shaders/filters/grain.glsl", "", { { "Amount", 0.5} }), + FilteringProgram("Sharpen", "shaders/filters/sharp.glsl", "", { { "Amount", 0.5} }), + FilteringProgram("Unsharp Mask", "shaders/filters/sharpen_1.glsl", "shaders/filters/sharpen_2.glsl", { { "Amount", 0.5} }), + FilteringProgram("Sharp Edge", "shaders/filters/bilinear.glsl", "shaders/filters/sharpenedge.glsl", { { "Strength", 0.5} }), + FilteringProgram("Edge", "shaders/filters/bilinear.glsl", "shaders/filters/edge.glsl", { { "Threshold", 0.5} }), + FilteringProgram("Sobel", "shaders/filters/sobel.glsl", "", { { "Factor", 0.5} }), + FilteringProgram("Freichen", "shaders/filters/freichen.glsl", "", { { "Factor", 0.5} }), + FilteringProgram("Pixelate", "shaders/filters/pixelate.glsl", "", { { "Size", 0.5}, { "Sharpen", 0.5} }), + FilteringProgram("Erosion", "shaders/filters/erosion.glsl", "", { { "Radius", 0.5} }), + FilteringProgram("Dilation", "shaders/filters/dilation.glsl", "", { { "Radius", 0.5} }), + FilteringProgram("Openning", "shaders/filters/erosion.glsl", "shaders/filters/dilation.glsl", { { "Radius", 0.5} }), + FilteringProgram("Closing", "shaders/filters/dilation.glsl", "shaders/filters/erosion.glsl", { { "Radius", 0.5} }), + FilteringProgram("TopHat", "shaders/filters/erosion.glsl", "shaders/filters/tophat.glsl", { { "Radius", 0.5} }), + FilteringProgram("BlackHat", "shaders/filters/dilation.glsl", "shaders/filters/blackhat.glsl", { { "Radius", 0.5} }), + FilteringProgram("Bloom", "shaders/filters/bloom.glsl", "", { { "Intensity", 0.5} }), + FilteringProgram("Bokeh", "shaders/filters/bokeh.glsl", "", { { "Radius", 1.0} }), + FilteringProgram("Chalk", "shaders/filters/talk.glsl", "", { { "Factor", 1.0} }), + FilteringProgram("Stippling","shaders/filters/stippling.glsl", "", { { "Factor", 0.5} }), + FilteringProgram("Dithering","shaders/filters/dithering.glsl", "", { { "Factor", 0.5} }), + FilteringProgram("Chromakey","shaders/filters/chromakey.glsl", "", { { "Red", 0.05}, { "Green", 0.63}, { "Blue", 0.14}, { "Tolerance", 0.54} }), + FilteringProgram("Fisheye", "shaders/filters/fisheye.glsl", "", { { "Factor", 0.35} }), }; -std::string ImageFilter::getFilterCodeInputs() +std::string FilteringProgram::getFilterCodeInputs() { static std::string filterHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\n" "float iTime; // shader playback time (in seconds)\n" @@ -121,42 +123,43 @@ std::string ImageFilter::getFilterCodeInputs() return filterHeaderHelp; } -std::string ImageFilter::getFilterCodeDefault() +std::string FilteringProgram::getFilterCodeDefault() { return filterDefault; } -ImageFilter::ImageFilter() : name_("None"), code_({"shaders/filters/default.glsl",""}) +FilteringProgram::FilteringProgram() : name_("None"), code_({"shaders/filters/default.glsl",""}), two_pass_filter_(false) { } -ImageFilter::ImageFilter(const std::string &name, const std::string &first_pass, const std::string &second_pass, +FilteringProgram::FilteringProgram(const std::string &name, const std::string &first_pass, const std::string &second_pass, const std::map ¶meters) : name_(name), code_({first_pass, second_pass}), parameters_(parameters) { - + two_pass_filter_ = !second_pass.empty(); } -ImageFilter::ImageFilter(const ImageFilter &other) : - name_(other.name_), code_(other.code_), parameters_(other.parameters_) +FilteringProgram::FilteringProgram(const FilteringProgram &other) : + name_(other.name_), code_(other.code_), parameters_(other.parameters_), two_pass_filter_(other.two_pass_filter_) { } -ImageFilter& ImageFilter::operator= (const ImageFilter& other) +FilteringProgram& FilteringProgram::operator= (const FilteringProgram& other) { if (this != &other) { this->name_ = other.name_; this->code_ = other.code_; this->parameters_.clear(); this->parameters_ = other.parameters_; + this->two_pass_filter_ = other.two_pass_filter_; } return *this; } -std::pair< std::string, std::string > ImageFilter::code() +std::pair< std::string, std::string > FilteringProgram::code() { // code for filter can be provided by the name of a ressource file if (Resource::hasPath(code_.first)) @@ -167,7 +170,7 @@ std::pair< std::string, std::string > ImageFilter::code() return code_; } -bool ImageFilter::operator!= (const ImageFilter& other) const +bool FilteringProgram::operator!= (const FilteringProgram& other) const { if (this->code_.first != other.code_.first) return true; @@ -306,58 +309,84 @@ void ImageFilteringShader::copy(ImageFilteringShader const& S) } -ImageFilterRenderer::ImageFilterRenderer(glm::vec3 resolution): enabled_(false) +ImageFilter::ImageFilter (): FrameBufferFilter(), buffers_({nullptr, nullptr}) { + // surface and shader for first pass shaders_.first = new ImageFilteringShader; surfaces_.first = new Surface(shaders_.first); - buffers_.first = new FrameBuffer( resolution, true ); - shaders_.second = nullptr; - surfaces_.second = nullptr; - buffers_.second = nullptr; + // surface and shader for second pass + shaders_.second = new ImageFilteringShader; + surfaces_.second = new Surface(shaders_.second); } -ImageFilterRenderer::~ImageFilterRenderer() +ImageFilter::~ImageFilter () { if ( buffers_.first!= nullptr ) delete buffers_.first; if ( buffers_.second!= nullptr ) delete buffers_.second; - if ( surfaces_.first!= nullptr ) - delete surfaces_.first; - if ( surfaces_.second!= nullptr ) - delete surfaces_.second; - + delete surfaces_.first; + delete surfaces_.second; // NB: shaders_ are removed with surface } -void ImageFilterRenderer::update(float dt) +void ImageFilter::update (float dt) { shaders_.first->update(dt); - if (shaders_.second) + + if ( program_.isTwoPass() ) shaders_.second->update(dt); } -void ImageFilterRenderer::setInputTexture(uint t) +uint ImageFilter::texture () const { - surfaces_.first->setTextureIndex( t ); - shaders_.first->mask_texture = t; - if (shaders_.second) - shaders_.second->mask_texture = t; + if (buffers_.first && buffers_.second) + return program_.isTwoPass() ? buffers_.second->texture() : buffers_.first->texture(); + + if (input_) + return input_->texture(); + + return Resource::getTextureBlack(); } -uint ImageFilterRenderer::getOutputTexture() const +glm::vec3 ImageFilter::resolution () const { - if ( enabled_ ) - return buffers_.second ? buffers_.second->texture() : buffers_.first->texture(); - else - return surfaces_.first->textureIndex(); + if (buffers_.first && buffers_.second) + return program_.isTwoPass() ? buffers_.second->resolution() : buffers_.first->resolution(); + + if (input_) + return input_->resolution(); + + return glm::vec3(1,1,0); } -void ImageFilterRenderer::draw() +void ImageFilter::draw (FrameBuffer *input) { - if ( enabled_ ) + // if input changed (typically on first draw) + if (input_ != input) { + // keep reference to input framebuffer + input_ = input; + // create first-pass surface and shader, taking as texture the input framebuffer + surfaces_.first->setTextureIndex( input_->texture() ); + shaders_.first->mask_texture = input_->texture(); + // (re)create framebuffer for result of first-pass + if (buffers_.first != nullptr) + delete buffers_.first; + buffers_.first = new FrameBuffer( input_->resolution(), input_->use_alpha() ); + // enforce framebuffer if first-pass is created now, filled with input framebuffer + input_->blit( buffers_.first ); + // create second-pass surface and shader, taking as texture the first-pass framebuffer + surfaces_.second->setTextureIndex( buffers_.first->texture() ); + shaders_.second->mask_texture = input_->texture(); + // (re)create framebuffer for result of second-pass + if (buffers_.second != nullptr) + delete buffers_.second; + buffers_.second = new FrameBuffer( buffers_.first->resolution(), buffers_.first->use_alpha() ); + } + + if ( enabled() ) { // FIRST PASS // render input surface into frame buffer @@ -366,9 +395,8 @@ void ImageFilterRenderer::draw() buffers_.first->end(); // SECOND PASS - if ( buffers_.second && surfaces_.second ) { + if ( program_.isTwoPass() ) { // render filtered surface from first pass into frame buffer - surfaces_.second->setTextureIndex( buffers_.first->texture() ); buffers_.second->begin(); surfaces_.second->draw(glm::identity(), buffers_.second->projection()); buffers_.second->end(); @@ -376,57 +404,42 @@ void ImageFilterRenderer::draw() } } -ImageFilter ImageFilterRenderer::filter() const +void ImageFilter::accept (Visitor& v) { - return filter_; + FrameBufferFilter::accept(v); + v.visit(*this); } -void ImageFilterRenderer::setFilter(const ImageFilter &f, std::promise *ret) + +FilteringProgram ImageFilter::program () const +{ + return program_; +} + +void ImageFilter::setProgram(const FilteringProgram &f, std::promise *ret) { // always keep local copy - filter_ = f; + program_ = f; - // force disable when using default filter - enabled_ = filter_ != ImageFilter::presets.front(); +// // force disable when using default filter +// setEnabled( program_ != FilteringProgram::presets.front() ); // change code - std::pair codes = filter_.code(); + std::pair codes = program_.code(); - // set code for first pass + // FIRST PASS + // set code to the shader for first-pass shaders_.first->setCode( codes.first, ret ); - - // no code provided for second pass - if ( codes.second.empty() ) { - - // delete second pass if was previously used - if (buffers_.second!= nullptr ) - delete buffers_.second; - if (surfaces_.second!= nullptr ) - delete surfaces_.second; - - shaders_.second = nullptr; - surfaces_.second = nullptr; - buffers_.second = nullptr; - } - // set code for second pass - else { - // second pass not setup - if (shaders_.second == nullptr) { - - // create shader, surface and buffer for second pass - shaders_.second = new ImageFilteringShader; - surfaces_.second = new Surface(shaders_.second); - buffers_.second = new FrameBuffer( buffers_.first->resolution(), true ); - } - - // set the code of the shader for second pass - shaders_.second->setCode( codes.second ); - } - // change uniforms - shaders_.first->uniforms_ = filter_.parameters(); - if (shaders_.second != nullptr ) - shaders_.second->uniforms_ = filter_.parameters(); + shaders_.first->uniforms_ = program_.parameters(); + + // SECOND PASS + if ( program_.isTwoPass() ) { + // set the code to the shader for second-pass + shaders_.second->setCode( codes.second ); + // change uniforms + shaders_.second->uniforms_ = program_.parameters(); + } } diff --git a/ImageFilter.h b/ImageFilter.h index 2c2debf..36ee01d 100644 --- a/ImageFilter.h +++ b/ImageFilter.h @@ -4,66 +4,69 @@ #include #include #include +#include + #include -#include -#include "ImageShader.h" +#include "FrameBufferFilter.h" -class Surface; -class FrameBuffer; -class ImageFilteringShader; - - -class ImageFilter +class FilteringProgram { std::string name_; - // code resource file or GLSL (ShaderToy style) + // code : resource file or GLSL (ShaderToy style) std::pair< std::string, std::string > code_; - // list of parameters (uniforms names and values) + // true if code is given for second pass + bool two_pass_filter_; + + // list of parameters : uniforms names and values std::map< std::string, float > parameters_; public: - ImageFilter(); - ImageFilter(const std::string &name, const std::string &first_pass, const std::string &second_pass, - const std::map ¶meters); - ImageFilter(const ImageFilter &other); + FilteringProgram(); + FilteringProgram(const std::string &name, const std::string &first_pass, const std::string &second_pass, + const std::map ¶meters); + FilteringProgram(const FilteringProgram &other); - ImageFilter& operator= (const ImageFilter& other); - bool operator!= (const ImageFilter& other) const; + FilteringProgram& operator= (const FilteringProgram& other); + bool operator!= (const FilteringProgram& other) const; - // get the name of the filter + // get the name inline std::string name() const { return name_; } - // set the code of the filter + // set the code inline void setCode(const std::pair< std::string, std::string > &code) { code_ = code; } - // get the code of the filter + // get the code std::pair< std::string, std::string > code(); - // set the list of parameters of the filter + // if has second pass + bool isTwoPass() const { return two_pass_filter_; } + + // set the list of parameters inline void setParameters(const std::map< std::string, float > ¶meters) { parameters_ = parameters; } - // get the list of parameters of the filter + // get the list of parameters inline std::map< std::string, float > parameters() const { return parameters_; } - // set a value of a parameter + // set the value of a parameter inline void setParameter(const std::string &p, float value) { parameters_[p] = value; } // globals static std::string getFilterCodeInputs(); static std::string getFilterCodeDefault(); - static std::list< ImageFilter > presets; + static std::list< FilteringProgram > presets; }; +class Surface; +class FrameBuffer; class ImageFilteringShader; -class ImageFilterRenderer +class ImageFilter : public FrameBufferFilter { - ImageFilter filter_; - bool enabled_; + FilteringProgram program_; std::pair< Surface *, Surface *> surfaces_; std::pair< FrameBuffer *, FrameBuffer * > buffers_; @@ -71,30 +74,23 @@ class ImageFilterRenderer public: - // instanciate an image filter at given resolution, with alpha channel - ImageFilterRenderer(glm::vec3 resolution); - ~ImageFilterRenderer(); + // instanciate an image filter at given resolution + ImageFilter(); + ~ImageFilter(); - inline void setEnabled (bool on) { enabled_ = on; } - inline bool enabled () const { return enabled_; } + // set the program + void setProgram(const FilteringProgram &f, std::promise *ret = nullptr); - void update (float dt); - - // set the texture to draw into the framebuffer - void setInputTexture(uint t); - - // draw the input texture with filter on the framebuffer - void draw(); - - // get the texture id of the rendered framebuffer - uint getOutputTexture() const; - - // set the filter - void setFilter(const ImageFilter &f, std::promise *ret = nullptr); - - // get copy of the filter - ImageFilter filter() const; + // get copy of the program + FilteringProgram program() const; + // implementation of FrameBufferFilter + Type type() const { return FrameBufferFilter::FILTER_IMAGE; } + uint texture () const override; + glm::vec3 resolution () const override; + void update (float dt) override; + void draw (FrameBuffer *input) override; + void accept (Visitor& v) override; }; diff --git a/InfoVisitor.cpp b/InfoVisitor.cpp index b4fe5d7..27b40fb 100644 --- a/InfoVisitor.cpp +++ b/InfoVisitor.cpp @@ -237,7 +237,7 @@ void InfoVisitor::visit (CloneSource& s) if (s.origin()) oss << "Clone of '" << s.origin()->name() << "' " << std::endl; oss << (s.frame()->use_alpha() ? "RGBA, " : "RGB, "); - oss << (int)(s.delay()*1000.0) << " ms delay " << std::endl; + oss << FrameBufferFilter::type_label[s.filter()->type()] << " filter" << std::endl; oss << s.frame()->width() << " x " << s.frame()->height(); } } diff --git a/SessionCreator.cpp b/SessionCreator.cpp index d59b746..524b1af 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -27,6 +27,9 @@ #include "Source.h" #include "SourceCallback.h" #include "CloneSource.h" +#include "FrameBufferFilter.h" +#include "DelayFilter.h" +#include "ImageFilter.h" #include "MediaSource.h" #include "SessionSource.h" #include "StreamSource.h" @@ -1210,14 +1213,71 @@ void SessionLoader::visit (SrtReceiverSource& s) } } +void SessionLoader::visit (FrameBufferFilter&) +{ -void SessionLoader::visit (CloneSource& s) +} + +void SessionLoader::visit (DelayFilter& f) { double d = 0.0; xmlCurrent_->QueryDoubleAttribute("delay", &d); - s.setDelay(d); + f.setDelay(d); } +void SessionLoader::visit (ImageFilter& f) +{ + const char * filter_name; + std::pair< std::string, std::string > filter_codes; + std::map< std::string, float > filter_params; + + xmlCurrent_->QueryStringAttribute("name", &filter_name); + + XMLElement* firstpass = xmlCurrent_->FirstChildElement("firstpass"); + if (firstpass) { + const char * code = firstpass->GetText(); + if (code) + filter_codes.first = std::string(code); + } + + XMLElement* secondpass = xmlCurrent_->FirstChildElement("secondpass"); + if (secondpass) { + const char * code = secondpass->GetText(); + if (code) + filter_codes.second = std::string(code); + } + + XMLElement* parameters = xmlCurrent_->FirstChildElement("parameters"); + if (parameters) { + XMLElement* param = parameters->FirstChildElement("uniform"); + for( ; param ; param = param->NextSiblingElement()) + { + float val = 0.f; + param->QueryFloatAttribute("value", &val); + const char * name; + param->QueryStringAttribute("name", &name); + if (name) + filter_params[name] = val; + } + } + + f.setProgram( FilteringProgram(filter_name, filter_codes.first, filter_codes.second, filter_params) ); +} + +void SessionLoader::visit (CloneSource& s) +{ + // configuration of filter in clone + xmlCurrent_ = xmlCurrent_->FirstChildElement("Filter"); + if (xmlCurrent_) { + // get type of filter and create + int t = 0; + xmlCurrent_->QueryIntAttribute("type", &t); + s.setFilter( FrameBufferFilter::Type(t) ); + + // set config filter + s.filter()->accept(*this); + } +} void SessionLoader::visit (SourceCallback &) { diff --git a/SessionCreator.h b/SessionCreator.h index a1870ac..1703c55 100644 --- a/SessionCreator.h +++ b/SessionCreator.h @@ -54,7 +54,6 @@ public: void visit (SessionFileSource& s) override; void visit (SessionGroupSource& s) override; void visit (RenderSource& s) override; - void visit (CloneSource& s) override; void visit (PatternSource& s) override; void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; @@ -62,6 +61,11 @@ public: void visit (GenericStreamSource& s) override; void visit (SrtReceiverSource& s) override; + void visit (CloneSource& s) override; + void visit (FrameBufferFilter&) override; + void visit (DelayFilter&) override; + void visit (ImageFilter&) override; + // callbacks void visit (SourceCallback&) override; void visit (SetAlpha&) override; diff --git a/SessionVisitor.cpp b/SessionVisitor.cpp index 7c4f7a5..0968526 100644 --- a/SessionVisitor.cpp +++ b/SessionVisitor.cpp @@ -30,6 +30,9 @@ using namespace tinyxml2; #include "Source.h" #include "SourceCallback.h" #include "CloneSource.h" +#include "FrameBufferFilter.h" +#include "DelayFilter.h" +#include "ImageFilter.h" #include "RenderSource.h" #include "MediaSource.h" #include "Session.h" @@ -510,13 +513,6 @@ void SessionVisitor::visit(ImageProcessingShader &n) xmlCurrent_->InsertEndChild(chromakey); } - -void SessionVisitor::visit(ImageFilter &f) -{ - - -} - void SessionVisitor::visit(LineStrip &n) { // Node of a different type @@ -688,10 +684,52 @@ void SessionVisitor::visit (RenderSource& s) xmlCurrent_->SetAttribute("provenance", (int) s.renderingProvenance()); } +void SessionVisitor::visit (FrameBufferFilter& f) +{ + xmlCurrent_->SetAttribute("type", (int) f.type()); +} + +void SessionVisitor::visit (DelayFilter& f) +{ + xmlCurrent_->SetAttribute("delay", f.delay()); +} + +void SessionVisitor::visit (ImageFilter& f) +{ + xmlCurrent_->SetAttribute("name", f.program().name().c_str() ); + + std::pair< std::string, std::string > filter_codes = f.program().code(); + std::map< std::string, float > filter_params = f.program().parameters(); + + XMLElement *firstpass = xmlDoc_->NewElement( "firstpass" ); + xmlCurrent_->InsertEndChild(firstpass); + { + XMLText *code = xmlDoc_->NewText( filter_codes.first.c_str() ); + firstpass->InsertEndChild(code); + } + + XMLElement *secondpass = xmlDoc_->NewElement( "secondpass" ); + xmlCurrent_->InsertEndChild(secondpass); + { + XMLText *code = xmlDoc_->NewText( filter_codes.second.c_str() ); + secondpass->InsertEndChild(code); + } + + XMLElement *parameters = xmlDoc_->NewElement( "parameters" ); + xmlCurrent_->InsertEndChild(parameters); + for (auto p = filter_params.begin(); p != filter_params.end(); ++p) + { + XMLElement *param = xmlDoc_->NewElement( "uniform" ); + param->SetAttribute("name", p->first.c_str() ); + param->SetAttribute("value", p->second ); + parameters->InsertEndChild(param); + } +} + void SessionVisitor::visit (CloneSource& s) { XMLElement *cloneNode = xmlCurrent_; - xmlCurrent_->SetAttribute("type", "CloneSource"); + cloneNode->SetAttribute("type", "CloneSource"); // origin of clone if (s.origin()) { @@ -702,13 +740,10 @@ void SessionVisitor::visit (CloneSource& s) origin->InsertEndChild( text ); } - // Delay - xmlCurrent_->SetAttribute( "delay", (double) s.delay()); - // Filter -// xmlCurrent_ = xmlDoc_->NewElement( "ImageFilter" ); -// cloneNode->InsertEndChild(xmlCurrent_); -//// s.filteringShader()->accept(*this); // TODO save ImageFilter + xmlCurrent_ = xmlDoc_->NewElement( "Filter" ); + s.filter()->accept(*this); + cloneNode->InsertEndChild(xmlCurrent_); xmlCurrent_ = cloneNode; // parent for next visits (other subtypes of Source) } diff --git a/SessionVisitor.h b/SessionVisitor.h index 102dc4a..e6fa10d 100644 --- a/SessionVisitor.h +++ b/SessionVisitor.h @@ -54,7 +54,6 @@ public: void visit (ImageShader& n) override; void visit (MaskShader& n) override; void visit (ImageProcessingShader& n) override; - void visit (ImageFilter& n) override; // Sources void visit (Source& s) override; @@ -62,7 +61,6 @@ public: void visit (SessionFileSource& s) override; void visit (SessionGroupSource& s) override; void visit (RenderSource&) override; - void visit (CloneSource& s) override; void visit (PatternSource& s) override; void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; @@ -71,6 +69,11 @@ public: void visit (GenericStreamSource& s) override; void visit (SrtReceiverSource& s) override; + void visit (CloneSource& s) override; + void visit (FrameBufferFilter&) override; + void visit (DelayFilter&) override; + void visit (ImageFilter&) override; + // callbacks void visit (SourceCallback&) override; void visit (SetAlpha&) override; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 8a03008..ec8214c 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -5377,18 +5377,25 @@ void ShaderEditor::Render() if (ImGui::MenuItem( ICON_FA_HAMMER " Build", nullptr, nullptr, current_ != nullptr )) { + // the UI has ref to code for this clone if (current_ != nullptr && filters_.find(current_) != filters_.end()) { - // set the code of the current filter - filters_[current_].setCode( { _editor.GetText(), "" } ); - // change the filter of the current clone - // => this triggers compilation of shader - compilation_ = new std::promise(); - current_->setFilter( filters_[current_], compilation_ ); - compilation_return_ = compilation_->get_future(); + ImageFilter *i = dynamic_cast( current_->filter() ); + // if we can access the code of inside the image filter + if (i) { + // set the code of the current filter + filters_[current_].setCode( { _editor.GetText(), "" } ); - // inform status - status_ = "Building..."; + // change the filter of the current image filter + // => this triggers compilation of shader + compilation_ = new std::promise(); + i->setProgram( filters_[current_], compilation_ ); + compilation_return_ = compilation_->get_future(); + + // inform status + status_ = "Building..."; + + } } } @@ -5441,14 +5448,22 @@ void ShaderEditor::Render() // get current clone source CloneSource *c = nullptr; Source *s = Mixer::manager().currentSource(); + // if there is a current source if (s != nullptr) { - // there is a current source c = dynamic_cast(s); + // if the current source is a clone if ( c != nullptr ) { - // the current source is a clone -// if ( filters_.find(c) == filters_.end() ) - // the current clone was not registered - filters_[c] = c->filter(); + FrameBufferFilter *f = c->filter(); + // if the filter seems to be an Image Filter + if (f && f->type() == FrameBufferFilter::FILTER_IMAGE ) { + ImageFilter *i = dynamic_cast(f); + // if we can access the code of the filter + if (i) { + // set code for this clone + filters_[c] = i->program(); + } + + } } else status_ = "No shader"; @@ -5491,7 +5506,7 @@ void ShaderEditor::Render() // render shader input if (show_shader_inputs_) { ImGuiTextBuffer info; - info.append(ImageFilter::getFilterCodeInputs().c_str()); + info.append(FilteringProgram::getFilterCodeInputs().c_str()); // Show info text bloc (multi line, dark background) ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO ); diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 6a92892..0c26493 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -394,7 +394,7 @@ class ShaderEditor : public WorkspaceWindow CloneSource *current_; bool current_changed_; bool show_shader_inputs_; - std::map filters_; + std::map filters_; std::promise *compilation_; std::future compilation_return_; diff --git a/Visitor.h b/Visitor.h index 00edae2..14f019b 100644 --- a/Visitor.h +++ b/Visitor.h @@ -26,7 +26,6 @@ class Shader; class ImageShader; class MaskShader; class ImageProcessingShader; -class ImageFilter; class Source; class MediaSource; @@ -42,6 +41,11 @@ class NetworkSource; class MixingGroup; class MultiFileSource; +class FrameBufferFilter; +class PassthroughFilter; +class DelayFilter; +class ImageFilter; + class SourceCallback; class SetAlpha; class SetDepth; @@ -79,7 +83,6 @@ public: virtual void visit (ImageShader&) {} virtual void visit (MaskShader&) {} virtual void visit (ImageProcessingShader&) {} - virtual void visit (ImageFilter&) {} // utility virtual void visit (Stream&) {} @@ -98,6 +101,11 @@ public: virtual void visit (CloneSource&) {} virtual void visit (MultiFileSource&) {} + virtual void visit (FrameBufferFilter&) {} + virtual void visit (PassthroughFilter&) {} + virtual void visit (DelayFilter&) {} + virtual void visit (ImageFilter&) {} + virtual void visit (SourceCallback&) {} virtual void visit (SetAlpha&) {} virtual void visit (SetDepth&) {}