From df0913727c7ad2da275fb5b67dbb9dce8f6a2940 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Tue, 10 Dec 2024 00:14:35 +0100 Subject: [PATCH] Improve Shader Editor with shader files Add a filename to FilteringProgram, and use the content of this file instead of code when file exists. Refer to this file in the menu of shader editor, when saving as or loading a file. Keep history of shader files in settings. --- src/ImageFilter.cpp | 26 +++---- src/ImageFilter.h | 8 ++- src/SessionCreator.cpp | 17 +++-- src/SessionVisitor.cpp | 1 + src/ShaderEditWindow.cpp | 142 ++++++++++++++++++++------------------- 5 files changed, 107 insertions(+), 87 deletions(-) diff --git a/src/ImageFilter.cpp b/src/ImageFilter.cpp index c3fbe9c..ef81692 100644 --- a/src/ImageFilter.cpp +++ b/src/ImageFilter.cpp @@ -104,20 +104,21 @@ glm::vec4 FilteringProgram::iMouse = glm::vec4(0.f,0.f,0.f,0.f); /// //// //////////////////////////////////////// -FilteringProgram::FilteringProgram() : name_("Default"), code_({"shaders/filters/default.glsl",""}), two_pass_filter_(false) +FilteringProgram::FilteringProgram() : name_("Default"), filename_(""), + code_({"shaders/filters/default.glsl",""}), two_pass_filter_(false) { } 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) + const std::map ¶meters, const std::string &filename) : + name_(name), filename_(filename), code_({first_pass, second_pass}), parameters_(parameters) { two_pass_filter_ = !second_pass.empty(); } FilteringProgram::FilteringProgram(const FilteringProgram &other) : - name_(other.name_), 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_) { } @@ -126,6 +127,7 @@ FilteringProgram& FilteringProgram::operator= (const FilteringProgram& other) { if (this != &other) { this->name_ = other.name_; + this->filename_ = other.filename_; this->code_ = other.code_; this->parameters_.clear(); this->parameters_ = other.parameters_; @@ -143,9 +145,6 @@ std::pair< std::string, std::string > FilteringProgram::code() if (Resource::hasPath(code_.second)) code_.second = Resource::getText(code_.second); - // if (SystemToolkit::file_exists(code_.first)) - // code_.first = SystemToolkit::get_text_content(code_.first); - return code_; } @@ -447,19 +446,20 @@ void ImageFilter::setProgram(const FilteringProgram &f, std::promise codes = program_.code(); + // if program code is given by a filename, read the file + if (!program_.filename().empty() && SystemToolkit::file_exists(program_.filename())) + codes.first = SystemToolkit::get_text_content(program_.filename()); + // FIRST PASS // set code to the shader for first-pass - std::string __code = codes.first; - if (SystemToolkit::file_exists(__code)) - __code = SystemToolkit::get_text_content(__code); - shaders_.first->setCode( __code, ret ); + shaders_.first->setCode( codes.first, ret ); // Parse code to detect additional declaration of uniform variables // Search for "uniform float", a variable name, with possibly a '=' and float value - std::string glslcode(__code); + std::string glslcode(codes.first); std::smatch found_uniform; std::regex is_a_uniform(REGEX_UNIFORM_DECLARATION REGEX_VARIABLE_NAME REGEX_UNIFORM_VALUE); // loop over every uniform declarations in the GLSL code diff --git a/src/ImageFilter.h b/src/ImageFilter.h index 6926a1e..10a20cc 100644 --- a/src/ImageFilter.h +++ b/src/ImageFilter.h @@ -16,6 +16,7 @@ class FilteringProgram { std::string name_; + std::string filename_; // code : resource file or GLSL (ShaderToy style) std::pair< std::string, std::string > code_; @@ -30,7 +31,7 @@ public: FilteringProgram(); FilteringProgram(const std::string &name, const std::string &first_pass, const std::string &second_pass, - const std::map ¶meters); + const std::map ¶meters, const std::string &filename = std::string()); FilteringProgram(const FilteringProgram &other); FilteringProgram& operator= (const FilteringProgram& other); @@ -40,6 +41,11 @@ public: inline void setName(const std::string &name) { name_ = name; } inline std::string name() const { return name_; } + // get the filename + inline void resetFilename() { filename_ = std::string(); } + inline void setFilename(const std::string &filename) { filename_ = filename; } + inline std::string filename() const { return filename_; } + // set the code inline void setCode(const std::pair< std::string, std::string > &code) { code_ = code; } diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index 5f80bcd..a749fa6 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -1476,8 +1476,7 @@ std::map< std::string, float > get_parameters_(XMLElement* parameters) float val = 0.f; param->QueryFloatAttribute("value", &val); const char * name; - param->QueryStringAttribute("name", &name); - if (name) + if ( param->QueryStringAttribute("name", &name) == XML_SUCCESS && name != NULL) filter_params[name] = val; } } @@ -1532,10 +1531,17 @@ void SessionLoader::visit (AlphaFilter& f) void SessionLoader::visit (ImageFilter& f) { - const char * filter_name; std::pair< std::string, std::string > filter_codes; - xmlCurrent_->QueryStringAttribute("name", &filter_name); + const char *name = NULL; + std::string filter_name; + if (xmlCurrent_->QueryStringAttribute("name", &name) == XML_SUCCESS && name != NULL) + filter_name = std::string(name); + + const char *filename = NULL; + std::string filter_filename; + if (xmlCurrent_->QueryStringAttribute("filename", &filename) == XML_SUCCESS && filename != NULL) + filter_filename = std::string(filename); // image filter code XMLElement* firstpass = xmlCurrent_->FirstChildElement("firstpass"); @@ -1555,7 +1561,8 @@ void SessionLoader::visit (ImageFilter& f) std::map< std::string, float > filter_params = get_parameters_(xmlCurrent_->FirstChildElement("parameters")); // set image filter program and parameters - f.setProgram( FilteringProgram(filter_name, filter_codes.first, filter_codes.second, filter_params) ); + f.setProgram( FilteringProgram(filter_name, filter_codes.first, filter_codes.second, + filter_params, filter_filename) ); // set global iMouse XMLElement* imouse = xmlCurrent_->FirstChildElement("iMouse"); diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index 5040e24..19d91c2 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -800,6 +800,7 @@ void SessionVisitor::visit (AlphaFilter& f) void SessionVisitor::visit (ImageFilter& f) { xmlCurrent_->SetAttribute("name", f.program().name().c_str() ); + xmlCurrent_->SetAttribute("filename", f.program().filename().c_str() ); // image filter code std::pair< std::string, std::string > filter_codes = f.program().code(); diff --git a/src/ShaderEditWindow.cpp b/src/ShaderEditWindow.cpp index 05db86c..0097ee1 100644 --- a/src/ShaderEditWindow.cpp +++ b/src/ShaderEditWindow.cpp @@ -18,6 +18,7 @@ TextEditor _editor; #include "SystemToolkit.h" #include "CloneSource.h" #include "DialogToolkit.h" +#include "BaseToolkit.h" #include "UserInterfaceManager.h" #include "ShaderEditWindow.h" @@ -107,18 +108,14 @@ void ShaderEditWindow::BuildShader() // if the UI has a current clone, and ref to code for current clone is valid if (current_ != nullptr && filters_.find(current_) != filters_.end()) { - // set the code of the current filter - if (Settings::application.recentShaderCode.path.empty() - || !SystemToolkit::file_exists(Settings::application.recentShaderCode.path)) { - filters_[current_].setCode({_editor.GetText(), ""}); - filters_[current_].setName("Custom"); // TODO SET BY FILENAME - } - else { - filters_[current_].setCode({Settings::application.recentShaderCode.path, ""}); - filters_[current_].setName(SystemToolkit::base_filename(Settings::application.recentShaderCode.path)); - } + // get the editor text and remove trailing '\n' + std::string code = _editor.GetText(); + code = code.substr(0, code.size() -1); - // set parameters TODO VERIFY IT WORKS FOR LOADED FILE + // set the code to the current content of editor + filters_[current_].setCode({code, ""}); + + // set parameters filters_[current_].setParameters(current_->program().parameters()); // change the filter of the current image filter @@ -151,7 +148,7 @@ void ShaderEditWindow::Render() return; } - std::string file_to_open_ = ""; + std::string file_to_build_ = ""; Source *cs = Mixer::manager().currentSource(); // menu (no title bar) @@ -205,8 +202,9 @@ void ShaderEditWindow::Render() if (ImGui::MenuItem(LABEL_SHADER_EMBEDDED, NULL, Settings::application.recentShaderCode.path.empty())) { // cancel path of recent shader + filters_[current_].resetFilename(); Settings::application.recentShaderCode.assign(""); - // build with code + // build code BuildShader(); } @@ -221,7 +219,10 @@ void ShaderEditWindow::Render() Settings::application.recentShaderCode.path) == 0; if (ImGui::MenuItem(label.c_str(), NULL, selected)) { - file_to_open_ = *filename; + // set shader program to be a file + file_to_build_ = *filename; + // read file and display content in editor + _editor.SetText(SystemToolkit::get_text_content(file_to_build_)); } } } @@ -247,16 +248,8 @@ void ShaderEditWindow::Render() p != FilteringProgram::presets.end(); ++p) { if (ImGui::MenuItem(p->name().c_str())) { - // change the filter of the current image filter - // => this triggers compilation of shader - compilation_ = new std::promise(); - current_->setProgram(*p, compilation_); - compilation_return_ = compilation_->get_future(); - // inform status - status_ = "Building..."; - Refresh(); - // cancel path of recent shader - Settings::application.recentShaderCode.assign(""); + // copy text code into editor + _editor.SetText( p->code().first ); } } @@ -298,8 +291,21 @@ void ShaderEditWindow::Render() } // Build action menu - if (ImGui::MenuItem( ICON_FA_HAMMER " Build", CTRL_MOD "B", nullptr, current_ != nullptr )) + if (ImGui::MenuItem( ICON_FA_HAMMER " Build", CTRL_MOD "B", nullptr, current_ != nullptr )) { + + // if present, save the program file with current content of editor + if (!filters_[current_].filename().empty()) { + std::ofstream file(filters_[current_].filename()); + if (file.is_open()) + file << _editor.GetText(); + file.close(); + } + + // TODO Rebuild all clone sources with same custom shading file + + // build BuildShader(); + } ImGui::EndMenuBar(); } @@ -312,45 +318,40 @@ void ShaderEditWindow::Render() _editor.SetText(""); } - // File dialog Export code gives a filename to save to if (exportcodedialog.closed() && !exportcodedialog.path().empty()) { - // Open the file - std::ofstream file(exportcodedialog.path()); - // Save content to file + // set shader program to be a file + file_to_build_ = exportcodedialog.path(); + + // save the current content of editor into given file + std::ofstream file(file_to_build_); if (file.is_open()) file << _editor.GetText(); - - // Close the file file.close(); - // set shader code by file - file_to_open_ = exportcodedialog.path(); } // File dialog select code gives a filename to open if (selectcodedialog.closed() && !selectcodedialog.path().empty()) { - // set shader code by file - file_to_open_ = selectcodedialog.path(); + + // set shader program to be a file + file_to_build_ = selectcodedialog.path(); + + // read file and display content in editor + _editor.SetText(SystemToolkit::get_text_content(file_to_build_)); } - if ( !file_to_open_.empty() ) { + if ( current_ != nullptr && !file_to_build_.empty() ) { + + // ok editor + _editor.SetReadOnly(false); + _editor.SetColorizerEnable(true); // link to file - Settings::application.recentShaderCode.push(file_to_open_); - Settings::application.recentShaderCode.assign(file_to_open_); - - // read and display content - std::string filecontent = SystemToolkit::get_text_content(file_to_open_); - - // TODO = ACTIONS BUILD should save the file - if (!filecontent.empty()) { - // replace text of editor - _editor.SetText(filecontent); - _editor.SetReadOnly(false); - _editor.SetColorizerEnable(true); - } + filters_[current_].setFilename(file_to_build_); + Settings::application.recentShaderCode.push(file_to_build_); + Settings::application.recentShaderCode.assign(file_to_build_); // build with new code BuildShader(); @@ -385,7 +386,7 @@ void ShaderEditWindow::Render() if (i != nullptr) { // if the current clone was not already registered if ( filters_.find(i) == filters_.end() ) - // remember code for this clone + // remember program for this image filter filters_[i] = i->program(); } } @@ -413,17 +414,11 @@ void ShaderEditWindow::Render() // if switch to another shader code if ( i != nullptr ) { - std::string c = filters_[i].code().first; - if (SystemToolkit::file_exists(c)) { - Settings::application.recentShaderCode.push(c); - Settings::application.recentShaderCode.assign(c); - c = SystemToolkit::get_text_content(c); - } - else - Settings::application.recentShaderCode.assign(""); + // set current shader code menu + Settings::application.recentShaderCode.assign(filters_[i].filename()); // change editor - _editor.SetText( c ); + _editor.SetText( filters_[i].code().first ); _editor.SetReadOnly(false); _editor.SetColorizerEnable(true); status_ = "Ready"; @@ -446,26 +441,37 @@ void ShaderEditWindow::Render() ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); ImGui::Text("Status: %s", status_.c_str()); - // render filename and file close button - if (!Settings::application.recentShaderCode.path.empty()) { // if current is linked file + // Right-align on same line than status + // Display name of program for embedded code + if (filters_[current_].filename().empty()) { + // right-aligned in italics and greyed out + const float w = ImGui::GetContentRegionAvail().x - IMGUI_SAME_LINE; + ImVec2 txtsize = ImGui::CalcTextSize(filters_[current_].name().c_str(), NULL); + ImGui::SameLine(w - txtsize.x, 0); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8, 0.8, 0.8, 0.9f)); + ImGui::Text("%s", filters_[current_].name().c_str()); + ImGui::PopStyleColor(1); + } + // or Display filename and close button for shaders from file + else { const float w = ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeight(); - - // right-aligned filename, in italics and greyed out - ImVec2 txtsize = ImGui::CalcTextSize(Settings::application.recentShaderCode.path.c_str(), NULL); + ImVec2 txtsize = ImGui::CalcTextSize(filters_[current_].filename().c_str(), NULL); ImGui::SameLine(w - txtsize.x - IMGUI_SAME_LINE, 0); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8, 0.8, 0.8, 0.9f)); - ImGui::Text("%s", Settings::application.recentShaderCode.path.c_str()); + ImGui::Text("%s", filters_[current_].filename().c_str()); ImGui::PopStyleColor(1); // top right X icon to close the file ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); ImGui::SameLine(w, IMGUI_SAME_LINE); if (ImGuiToolkit::TextButton(ICON_FA_TIMES, "Close file")) { + // unset filename for program + filters_[current_].resetFilename(); // remove the filename from list of menu - Settings::application.recentShaderCode.remove( - Settings::application.recentShaderCode.path); - // assign a non-filename to path and rebuild from existing code as embeded + Settings::application.recentShaderCode.remove(Settings::application.recentShaderCode.path); + // assign a non-filename to path Settings::application.recentShaderCode.assign(""); + // rebuild from existing code as embeded BuildShader(); } ImGui::PopStyleVar(1);