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.
This commit is contained in:
Bruno Herbelin
2024-12-10 00:14:35 +01:00
parent fb99136cc1
commit df0913727c
5 changed files with 107 additions and 87 deletions

View File

@@ -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<std::string, float> &parameters) :
name_(name), code_({first_pass, second_pass}), parameters_(parameters)
const std::map<std::string, float> &parameters, 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<std::string
// always keep local copy
program_ = f;
// change code
// get code
std::pair<std::string, std::string> 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

View File

@@ -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<std::string, float> &parameters);
const std::map<std::string, float> &parameters, 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; }

View File

@@ -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");

View File

@@ -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();

View File

@@ -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<std::string>();
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);