From bdc313cd1fd7328c4c8ee5810b2ff02dfa12f227 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Sat, 8 Nov 2025 14:06:09 +0100 Subject: [PATCH] Add texture management as sampler2D uniform to ImageFilter and related components --- src/ImGuiVisitor.cpp | 36 +++++++++- src/ImageFilter.cpp | 138 +++++++++++++++++++++++++++++++++++++-- src/ImageFilter.h | 24 ++++++- src/SessionCreator.cpp | 22 ++++++- src/SessionVisitor.cpp | 17 +++++ src/ShaderEditWindow.cpp | 1 + 6 files changed, 231 insertions(+), 7 deletions(-) diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index 495696d..4d5d6e0 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -1293,6 +1293,39 @@ void ImGuiVisitor::visit (AlphaFilter& f) } + +void list_textures_(ImageFilter &f, std::ostringstream &oss) +{ + std::map filter_textures = f.program().textures(); + + for (auto tex = filter_textures.rbegin(); tex != filter_textures.rend(); ++tex) + { + ImGui::PushID( tex->first.c_str() ); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + + Source *source = Mixer::manager().findSource( tex->second ); + std::string label = source == nullptr ? "Select source" : "" + + std::string( source->initials() ) + + " - " + + source->name(); + if (ImGui::BeginCombo(tex->first.c_str(), label.c_str()) ) + { + SourceList::iterator iter; + for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter) + { + label = std::string((*iter)->initials()) + " - " + (*iter)->name(); + if (ImGui::Selectable( label.c_str())) { + f.setProgramTexture(tex->first, (*iter)->id() ); + oss << " Texture " << tex->first << " " << label; + Action::manager().store(oss.str()); + } + } + ImGui::EndCombo(); + } + ImGui::PopID(); + } +} + void ImGuiVisitor::visit (ImageFilter& f) { // Open Editor @@ -1301,9 +1334,10 @@ void ImGuiVisitor::visit (ImageFilter& f) ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::Text("Code"); - // List of parameters + // List of parameters & textures oss << "Custom "; list_parameters_(f, oss); + list_textures_(f, oss); } diff --git a/src/ImageFilter.cpp b/src/ImageFilter.cpp index c284a5f..d92ec34 100644 --- a/src/ImageFilter.cpp +++ b/src/ImageFilter.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ #include "Resource.h" #include "SystemToolkit.h" #include "Visitor.h" +#include "Source.h" #include "Mixer.h" @@ -120,14 +122,17 @@ FilteringProgram::FilteringProgram() : name_("Default"), filename_(""), } FilteringProgram::FilteringProgram(const std::string &name, const std::string &first_pass, const std::string &second_pass, - const std::map ¶meters, const std::string &filename) : - name_(name), filename_(filename), code_({first_pass, second_pass}), parameters_(parameters) + const std::map ¶meters, const std::string &filename, + const std::map &textures ) : + name_(name), filename_(filename), code_({first_pass, second_pass}), parameters_(parameters), textures_(textures) { two_pass_filter_ = !second_pass.empty(); } FilteringProgram::FilteringProgram(const FilteringProgram &other) : - name_(other.name_), filename_(other.filename_), code_(other.code_), two_pass_filter_(other.two_pass_filter_), parameters_(other.parameters_) + name_(other.name_), filename_(other.filename_), code_(other.code_), + two_pass_filter_(other.two_pass_filter_), parameters_(other.parameters_), + textures_(other.textures_) { } @@ -140,6 +145,8 @@ FilteringProgram& FilteringProgram::operator= (const FilteringProgram& other) this->code_ = other.code_; this->parameters_.clear(); this->parameters_ = other.parameters_; + this->textures_.clear(); + this->textures_ = other.textures_; this->two_pass_filter_ = other.two_pass_filter_; } @@ -178,6 +185,17 @@ void FilteringProgram::removeParameter(const std::string &p) parameters_.erase(p); } +bool FilteringProgram::hasTexture(const std::string &t) +{ + return textures_.find(t) != textures_.end(); +} + +void FilteringProgram::removeTexture(const std::string &t) +{ + if (hasTexture(t)) + textures_.erase(t); +} + //////////////////////////////////////// ///// // @@ -251,11 +269,26 @@ void ImageFilteringShader::use() // uniform variable could be set, keep it ++u; else { - // uniform variable does not exist in code, remove it + // uniform variable is not used in code, remove it u = uniforms_.erase(u); uniforms_changed_ = true; } } + + // loop over sampler2D uniforms for channels (start at 2) + int sampler_index = 2; + for (auto u = sampler2D_.begin(); u != sampler2D_.end(); ) { + // set uniform sampler2D to current sampler_index + if ( program_->setUniform(u->first, sampler_index++) ) + // uniform variable could be set, keep it + ++u; + else { + // uniform variable is not used in code, remove it + u = sampler2D_.erase(u); + uniforms_changed_ = true; + } + } + } void ImageFilteringShader::reset() @@ -356,6 +389,15 @@ void ImageFilter::update (float dt) if (shaders_.first->uniforms_.count(param->first) < 1) program_.removeParameter(param->first); } + + // loop over the textures of the program... + std::map __T = program_.textures(); + for (auto tex = __T.begin(); tex != __T.end(); ++tex) { + // .. and remove the textures that are not valid sampler2D + if (shaders_.first->sampler2D_.count(tex->first) < 1) + program_.removeTexture(tex->first); + } + // done shaders_.first->uniforms_changed_ = false; } @@ -408,6 +450,8 @@ void ImageFilter::draw (FrameBuffer *input) if (buffers_.second != nullptr) delete buffers_.second; buffers_.second = new FrameBuffer( buffers_.first->resolution(), buffers_.first->flags() ); + // force update + updateParameters(); // forced draw forced = true; } @@ -417,6 +461,18 @@ void ImageFilter::draw (FrameBuffer *input) // FIRST PASS if (channel1_output_session) shaders_.first->secondary_texture = Mixer::manager().session()->frame()->texture(); + + // loop over sampler2D uniforms to bind textures + uint texture_unit = 0; + for (auto u = shaders_.first->sampler2D_.begin(); u != shaders_.first->sampler2D_.end(); ++u) { + // setup mask texture + glActiveTexture(GL_TEXTURE2 + texture_unit++); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture (GL_TEXTURE_2D, u->second); + glActiveTexture(GL_TEXTURE0); + } + // render input surface into frame buffer buffers_.first->begin(); surfaces_.first->draw(glm::identity(), buffers_.first->projection()); @@ -449,6 +505,7 @@ FilteringProgram ImageFilter::program () const #define REGEX_UNIFORM_DECLARATION "uniform\\s+float\\s+" #define REGEX_VARIABLE_NAME "[a-zA-Z_][\\w]+" #define REGEX_UNIFORM_VALUE "(\\s*=\\s*[[:digit:]]+(\\.[[:digit:]]*)?)?\\s*\\;" +#define REGEX_SAMPLER_DECLARATION "uniform\\s+sampler2D\\s+" void ImageFilter::setProgram(const FilteringProgram &f, std::promise *ret) { @@ -512,11 +569,35 @@ void ImageFilter::setProgram(const FilteringProgram &f, std::promiseuniforms_ = program_.parameters(); + // change textures into sampler2D + auto texturelist = program_.textures(); + if ( !texturelist.empty() ) { + auto copy_sampler2D = shaders_.first->sampler2D_; + for (auto T = texturelist.begin(); T != texturelist.end(); ++T) { + // get texture id from source + uint texture_id = Resource::getTextureBlack(); + Source *s = Mixer::manager().findSource(T->second); + if ( s != nullptr ) + texture_id = s->texture(); + + // set or insert a texture id into sampler2D list + shaders_.first->sampler2D_[T->first] = texture_id; + + // remove from copy list + if ( copy_sampler2D.find(T->first) != copy_sampler2D.end() ) { + copy_sampler2D.erase(T->first); + } + } + // remove textures that are not used anymore + for (auto S = copy_sampler2D.begin(); S != copy_sampler2D.end(); ++S) { + shaders_.first->sampler2D_.erase(S->first); + } + } + else { + // no texture, clear sampler2D list + shaders_.first->sampler2D_.clear(); + } + if ( program_.isTwoPass() ) shaders_.second->uniforms_ = program_.parameters(); } @@ -555,6 +665,26 @@ void ImageFilter::setProgramParameter(const std::string &p, float value) updateParameters(); } +void ImageFilter::setProgramTextures(const std::map< std::string, uint64_t > &textures) +{ + for (const auto &p : textures) { + if (p.first.empty()) + return; + } + + program_.setTextures(textures); + updateParameters(); +} + +void ImageFilter::setProgramTexture(const std::string &p, uint64_t id) +{ + if (p.empty()) + return; + + program_.setTexture(p, id); + updateParameters(); +} + //////////////////////////////////////// ///// // //// RESAMPLING FILTERS /// diff --git a/src/ImageFilter.h b/src/ImageFilter.h index 1c9d8c3..0b5659e 100644 --- a/src/ImageFilter.h +++ b/src/ImageFilter.h @@ -27,11 +27,15 @@ class FilteringProgram // list of parameters : uniforms names and values std::map< std::string, float > parameters_; + // list of texture inputs : uniform sampler2D names and source id + std::map< std::string, uint64_t > textures_; + public: FilteringProgram(); FilteringProgram(const std::string &name, const std::string &first_pass, const std::string &second_pass, - const std::map ¶meters, const std::string &filename = std::string()); + const std::map ¶meters, const std::string &filename = std::string(), + const std::map &textures = std::map() ); FilteringProgram(const FilteringProgram &other); FilteringProgram& operator= (const FilteringProgram& other); @@ -67,6 +71,18 @@ public: bool hasParameter(const std::string &p); void removeParameter(const std::string &p); + // set the list of textures + inline void setTextures(const std::map< std::string, uint64_t > &textures) { textures_ = textures; } + + // get the list of textures + inline std::map< std::string, uint64_t > textures() const { return textures_; } + + // get / set texture + inline void clearTextures() { textures_.clear(); } + inline void setTexture(const std::string &t, uint64_t id) { textures_[t] = id; } + bool hasTexture(const std::string &t); + void removeTexture(const std::string &t); + // globals static std::string getFilterCodeInputs(); static std::string getFilterCodeDefault(); @@ -95,6 +111,8 @@ public: // list of uniforms to control shader std::map< std::string, float > uniforms_; + // list of uniforms to control shader textures id + std::map< std::string, uint > sampler2D_; bool uniforms_changed_; ImageFilteringShader(); @@ -131,6 +149,10 @@ public: void setProgramParameters(const std::map< std::string, float > ¶meters); void setProgramParameter(const std::string &p, float value); + // update textures of program + void setProgramTextures(const std::map< std::string, uint64_t > &textures); + void setProgramTexture(const std::string &t, uint64_t id); + // implementation of FrameBufferFilter Type type() const override { return FrameBufferFilter::FILTER_IMAGE; } uint texture () const override; diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index 65ab337..3aaf932 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -1555,6 +1555,23 @@ void SessionLoader::visit (AlphaFilter& f) f.setProgramParameters( get_parameters_(xmlCurrent_->FirstChildElement("parameters")) ); } +std::map< std::string, uint64_t > get_textures_(XMLElement* textures) +{ + std::map< std::string, uint64_t > filter_params; + if (textures) { + XMLElement* param = textures->FirstChildElement("sampler2D"); + for( ; param ; param = param->NextSiblingElement()) + { + uint64_t val = 0.f; + param->QueryUnsigned64Attribute("id", &val); + const char * name; + if ( param->QueryStringAttribute("name", &name) == XML_SUCCESS && name != NULL) + filter_params[name] = val; + } + } + return filter_params; +} + void SessionLoader::visit (ImageFilter& f) { std::pair< std::string, std::string > filter_codes; @@ -1586,9 +1603,12 @@ void SessionLoader::visit (ImageFilter& f) // image filter parameters std::map< std::string, float > filter_params = get_parameters_(xmlCurrent_->FirstChildElement("parameters")); + // image filter textures + std::map< std::string, uint64_t > filter_textures = get_textures_(xmlCurrent_->FirstChildElement("textures")); + // set image filter program and parameters f.setProgram( FilteringProgram(filter_name, filter_codes.first, filter_codes.second, - filter_params, filter_filename) ); + filter_params, filter_filename, filter_textures) ); // set global iMouse XMLElement* imouse = xmlCurrent_->FirstChildElement("iMouse"); diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index b69d6f4..c198f7a 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -810,6 +810,20 @@ void SessionVisitor::visit (AlphaFilter& f) xmlCurrent_->InsertEndChild( list_parameters_(xmlDoc_, f.program().parameters()) ); } + +XMLElement *list_textures_(tinyxml2::XMLDocument *doc, std::map< std::string, uint64_t > filter_params) +{ + XMLElement *parameters = doc->NewElement( "textures" ); + for (auto p = filter_params.begin(); p != filter_params.end(); ++p) + { + XMLElement *param = doc->NewElement( "sampler2D" ); + param->SetAttribute("name", p->first.c_str() ); + param->SetAttribute("id", p->second ); + parameters->InsertEndChild(param); + } + return parameters; +} + void SessionVisitor::visit (ImageFilter& f) { xmlCurrent_->SetAttribute("name", f.program().name().c_str() ); @@ -834,6 +848,9 @@ void SessionVisitor::visit (ImageFilter& f) // image filter parameters xmlCurrent_->InsertEndChild( list_parameters_(xmlDoc_, f.program().parameters()) ); + // image filter texture inputs + xmlCurrent_->InsertEndChild( list_textures_(xmlDoc_, f.program().textures()) ); + // global iMouse XMLElement *imouse = xmlDoc_->NewElement("iMouse"); imouse->InsertEndChild( XMLElementFromGLM(xmlDoc_, FilteringProgram::iMouse) ); diff --git a/src/ShaderEditWindow.cpp b/src/ShaderEditWindow.cpp index 3f528f8..b017f55 100644 --- a/src/ShaderEditWindow.cpp +++ b/src/ShaderEditWindow.cpp @@ -167,6 +167,7 @@ void ShaderEditWindow::BuildShader() // set parameters filters_[current_].setParameters(current_->program().parameters()); + filters_[current_].setTextures(current_->program().textures()); // change the filter of the current image filter // => this triggers compilation of shader