diff --git a/CloneSource.cpp b/CloneSource.cpp index 87b9665..c442b80 100644 --- a/CloneSource.cpp +++ b/CloneSource.cpp @@ -229,9 +229,9 @@ void CloneSource::setDelay(double second) } -void CloneSource::setFilter(const ImageFilter &filter) +void CloneSource::setFilter(const ImageFilter &filter, std::promise *ret) { - filter_render_->setFilter(filter); + filter_render_->setFilter(filter, ret); } ImageFilter CloneSource::filter() const diff --git a/CloneSource.h b/CloneSource.h index 47a311e..a223409 100644 --- a/CloneSource.h +++ b/CloneSource.h @@ -36,7 +36,7 @@ public: void setDelay(double second); inline double delay() const { return delay_; } - void setFilter(const ImageFilter &filter); + void setFilter(const ImageFilter &filter, std::promise *ret = nullptr); ImageFilter filter() const; glm::ivec2 icon() const override; diff --git a/ImageFilter.cpp b/ImageFilter.cpp index 0ffcac5..ee26743 100644 --- a/ImageFilter.cpp +++ b/ImageFilter.cpp @@ -31,15 +31,14 @@ #include "ImageFilter.h" -std::string ShaderToyHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\n" - "float iTime; // shader playback time (in seconds)\n" - "float iTimeDelta; // render time (in seconds)\n" - "int iFrame; // shader playback frame\n" - "vec3 iChannelResolution[2]; // input channel resolution (in pixels)\n" - "vec4 iDate; // (year, month, day, time in seconds)\n" - "sampler2D iChannel0; // input channel (texture).\n" - "sampler2D iChannel1; // input channel (mask).\n" - "vec4 iDate; // (year, month, day, time in seconds)\n"; +std::string filterHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\n" + "float iTime; // shader playback time (in seconds)\n" + "float iTimeDelta; // render time (in seconds)\n" + "int iFrame; // shader playback frame\n" + "vec3 iChannelResolution[2]; // input channel resolution (in pixels)\n" + "sampler2D iChannel0; // input channel (texture).\n" + "sampler2D iChannel1; // input channel (mask).\n" + "vec4 iDate; // (year, month, day, time in seconds)"; std::string fragmentHeader = "#version 330 core\n" "out vec4 FragColor;\n" @@ -64,7 +63,7 @@ std::string filterDefault = "void mainImage( out vec4 fragColor, in vec2 fragCoo " fragColor = texture(iChannel0, uv);\n" "}\n"; -std::string fragmentFooter = "\nvoid main() {\n" +std::string fragmentFooter = "void main() {\n" " iChannelResolution[0] = vec3(textureSize(iChannel0, 0), 0.f);\n" " iChannelResolution[1] = vec3(textureSize(iChannel1, 0), 0.f);\n" " vec4 texcoord = iTransform * vec4(vertexUV.x, vertexUV.y, 0.0, 1.0);\n" @@ -77,7 +76,7 @@ class ImageFilteringShader : public Shader ShadingProgram custom_shading_; // fragment shader GLSL code - std::string code_; + std::string shader_code_; // list of uniform vars in GLSL // with associated pair (current_value, default_values) in range [0.f 1.f] @@ -99,7 +98,7 @@ public: void copy(ImageFilteringShader const& S); // set fragment shader code and uniforms (with default values) - void setFilterCode(const std::string &code, std::map parameters); + void setFilterCode(const std::string &code, std::map parameters, std::promise *prom = nullptr); }; @@ -162,16 +161,15 @@ void ImageFilteringShader::reset() // loop over all uniforms for (auto u = uniforms_.begin(); u != uniforms_.end(); ++u) - // reset current value to detault value + // reset current value to default value u->second.x = u->second.y; } -void ImageFilteringShader::setFilterCode(const std::string &code, std::map parameters) +void ImageFilteringShader::setFilterCode(const std::string &code, std::map parameters, std::promise *ret) { // change the shading code for fragment - code_ = fragmentHeader + code + fragmentFooter; - custom_shading_.setShaders("shaders/image.vs", code_); -// g_print("Filter code:\n%s", code_.c_str()); + shader_code_ = fragmentHeader + code + fragmentFooter; + custom_shading_.setShaders("shaders/image.vs", shader_code_, ret); // set the list of uniforms uniforms_.clear(); @@ -183,8 +181,8 @@ void ImageFilteringShader::setFilterCode(const std::string &code, std::map montecarloParam = { { "factor", 0.9 }, {"size", 0.1} }; - +std::string ImageFilter::getFilterCodeDefault() +{ + return filterDefault; +} ImageFilter::ImageFilter() : code_(filterDefault) { @@ -264,21 +219,18 @@ ImageFilter::ImageFilter(const ImageFilter &other) : code_(other.code_) ImageFilter& ImageFilter::operator = (const ImageFilter& other) { - if (this != &other) { + if (this != &other) this->code_ = other.code_; - } + return *this; } bool ImageFilter::operator != (const ImageFilter& other) const { - if (this != &other) { + if (this == &other || this->code_ == other.code_) + return false; - if (this->code_ != other.code_) - return true; - - } - return false; + return true; } @@ -292,7 +244,7 @@ ImageFilterRenderer::ImageFilterRenderer(glm::vec3 resolution): enabled_(true) buffer_ = new FrameBuffer( resolution, true ); std::map< std::string, float > paramfilter; - shader_->setFilterCode( filter_.code(), paramfilter); + shader_->setFilterCode( filter_.code(), paramfilter ); } ImageFilterRenderer::~ImageFilterRenderer() @@ -328,12 +280,15 @@ void ImageFilterRenderer::draw() } } -void ImageFilterRenderer::setFilter(const ImageFilter &f) +void ImageFilterRenderer::setFilter(const ImageFilter &f, std::promise *ret) { if (f != filter_) { // set to a different filter : apply change to shader std::map< std::string, float > paramfilter; - shader_->setFilterCode( f.code(), paramfilter ); + shader_->setFilterCode( f.code(), paramfilter, ret ); + } + else if (ret != nullptr) { + ret->set_value("No change."); } // keep new filter diff --git a/ImageFilter.h b/ImageFilter.h index c9c687b..0816943 100644 --- a/ImageFilter.h +++ b/ImageFilter.h @@ -28,6 +28,10 @@ public: // get the code of the filter inline std::string code() const { return code_; } + + // global + static std::string getFilterCodeInputs(); + static std::string getFilterCodeDefault(); }; @@ -59,7 +63,7 @@ public: uint getOutputTexture() const; // set the code of the filter - void setFilter(const ImageFilter &f); + void setFilter(const ImageFilter &f, std::promise *ret = nullptr); // get the code of the filter inline ImageFilter filter() const { return filter_; } diff --git a/Shader.cpp b/Shader.cpp index 37b4b40..d028b32 100644 --- a/Shader.cpp +++ b/Shader.cpp @@ -1,4 +1,4 @@ -/* +/* * This file is part of vimix - video live mixer * * **Copyright** (C) 2019-2022 Bruno Herbelin @@ -40,7 +40,7 @@ #include "Shader.h" #ifndef NDEBUG -#define SHADER_DEBUG +//#define SHADER_DEBUG #endif // Globals @@ -77,20 +77,22 @@ GLenum blending_destination_function[9] = {GL_ONE_MINUS_SRC_ALPHA,// normal GL_ZERO}; ShadingProgram::ShadingProgram(const std::string& vertex, const std::string& fragment) : - id_(0), need_compile_(true), vertex_(vertex), fragment_(fragment) + id_(0), need_compile_(true), vertex_(vertex), fragment_(fragment), promise_(nullptr) { } -void ShadingProgram::setShaders(const std::string& vertex, const std::string& fragment) +void ShadingProgram::setShaders(const std::string& vertex, const std::string& fragment, std::promise *prom) { vertex_ = vertex; fragment_ = fragment; need_compile_ = true; + promise_ = prom; } -bool ShadingProgram::compile() +void ShadingProgram::compile() { char infoLog[1024]; + infoLog[0] = '\0'; int success = GL_FALSE; std::string vertex_code = vertex_; @@ -109,7 +111,6 @@ bool ShadingProgram::compile() glGetShaderiv(vertex_id_, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertex_id_, 1024, NULL, infoLog); - Log::Warning("Error compiling Vertex ShadingProgram:\n%s", infoLog); } else { // FRAGMENT SHADER @@ -121,7 +122,6 @@ bool ShadingProgram::compile() glGetShaderiv(fragment_id_, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragment_id_, 1024, NULL, infoLog); - Log::Warning("Error compiling Fragment ShadingProgram:\n%s", infoLog); glDeleteShader(vertex_id_); } else { @@ -140,7 +140,6 @@ bool ShadingProgram::compile() glGetProgramiv(id_, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(id_, 1024, NULL, infoLog); - Log::Warning("Error linking ShadingProgram:\n%s", infoLog); glDeleteProgram(id_); id_ = 0; } @@ -161,11 +160,15 @@ bool ShadingProgram::compile() } } + // always fulfill a promise + if (promise_) + promise_->set_value( success ? "Ok" : "Error:\n" + std::string(infoLog) ); + // if not asked to return a promise, inform user through logs + else if (!success) + Log::Warning("Error compiling Vertex ShadingProgram:\n%s", infoLog); + // do not compile indefinitely need_compile_ = false; - - // inform of success - return success; } void ShadingProgram::use() diff --git a/Shader.h b/Shader.h index 35a3fbc..9b82b35 100644 --- a/Shader.h +++ b/Shader.h @@ -1,6 +1,7 @@ #ifndef __SHADER_H_ #define __SHADER_H_ +#include #include #include #include @@ -14,10 +15,13 @@ class ShadingProgram public: // create GLSL Program from resource file (if exist) or code of vertex and fragment shaders ShadingProgram(const std::string& vertex = "", const std::string& fragment = ""); - void setShaders(const std::string& vertex, const std::string& fragment); + + // Update GLSL Program with vertex and fragment program + // If a promise is given, it is filled during compilation with the compilation log. + void setShaders(const std::string& vertex, const std::string& fragment, std::promise *prom = nullptr); void use(); - bool compile(); + void compile(); static void enduse(); void reset(); @@ -30,6 +34,7 @@ private: bool need_compile_; std::string vertex_; std::string fragment_; + std::promise *promise_; static ShadingProgram *currentProgram_; }; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 14c6023..fd58045 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -18,16 +18,15 @@ **/ #include -#include +#include +#include #include +#include +#include #include #include #include #include -#include -#include -#include -#include using namespace std; @@ -5191,7 +5190,8 @@ void InputMappingInterface::Render() /// SHADER EDITOR /// /// -ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), current_changed_(true) +ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), + current_changed_(true), show_shader_inputs_(false) { auto lang = TextEditor::LanguageDefinition::GLSL(); @@ -5206,8 +5206,8 @@ ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), cur lang.mKeywords.insert(k); static const char* const identifiers[] = { - "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "pow", "exp2", "log2", "sqrt", "inversesqrt", - "abs", "sign", "floor", "ceil", "fract", "mod", "min", "max", "clamp", "mix", "step", "smoothstep", "length", "distance", + "radians", "degrees", "sin", "cos", "tan", "pow", "exp2", "log2", "sqrt", "inversesqrt", + "sign", "floor", "ceil", "fract", "mod", "min", "max", "clamp", "mix", "step", "smoothstep", "length", "distance", "dot", "cross", "normalize", "ftransform", "faceforward", "reflect", "matrixcompmult", "lessThan", "lessThanEqual", "greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", "not", "texture1D", "texture1DProj", "texture1DLod", "texture1DProjLod", "texture", "texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod", "texture3D", @@ -5218,7 +5218,18 @@ ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), cur for (auto& k : identifiers) { TextEditor::Identifier id; - id.mDeclaration = "Added function"; + id.mDeclaration = "GLSL function"; + lang.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + static const char* const filter_keyword[] = { + "iResolution", "iTime", "iTimeDelta", "iFrame", "iChannelResolution", "iDate", + "iChannel0", "iChannel1", "iTransform", "FragColor", "vertexColor", "vertexUV", + }; + for (auto& k : filter_keyword) + { + TextEditor::Identifier id; + id.mDeclaration = "Shader keyword"; lang.mIdentifiers.insert(std::make_pair(std::string(k), id)); } @@ -5226,6 +5237,9 @@ ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), cur _editor.SetLanguageDefinition(lang); _editor.SetHandleKeyboardInputs(true); _editor.SetText(""); + + // status + status_ = "-"; } void ShaderEditor::setVisible(bool on) @@ -5272,10 +5286,19 @@ void ShaderEditor::Render() Settings::application.widget.shader_editor = false; if (ImGui::BeginMenu(IMGUI_TITLE_SHADEREDITOR)) { + if (ImGui::MenuItem( ICON_FA_SYNC " Reload")) { + if (current_) + filters_.erase(current_); + current_ = nullptr; + } + // Enable/Disable editor options + ImGui::Separator(); + ImGui::MenuItem( ICON_FA_UNDERLINE " Show Shader Inputs", nullptr, &show_shader_inputs_); if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Show whitespace", nullptr, &ws)) _editor.SetShowWhitespaces(ws); + // Edit menu ImGui::Separator(); if (ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO, nullptr, !ro && _editor.CanUndo())) _editor.Undo(); @@ -5289,7 +5312,7 @@ void ShaderEditor::Render() _editor.Delete(); if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, nullptr, !ro && ImGui::GetClipboardText() != nullptr)) _editor.Paste(); - if (ImGui::MenuItem( "Select all", nullptr, nullptr)) + if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL, nullptr, _editor.GetText().size() > 1 )) _editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(_editor.GetTotalLines(), 0)); // output manager menu @@ -5308,10 +5331,21 @@ void ShaderEditor::Render() ImGui::EndMenu(); } - if (ImGui::MenuItem( " Compile ")) { - if (current_ != nullptr) + if (ImGui::MenuItem( ICON_FA_HAMMER " Build", nullptr, nullptr, current_ != nullptr )) { + + if (current_ != nullptr && filters_.find(current_) != filters_.end()) { + // set the code of the current filter filters_[current_].setCode(_editor.GetText()); - current_->setFilter( filters_[current_] ); + + // 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(); + + // inform status + status_ = "Building..."; + } } ImGui::EndMenuBar(); @@ -5326,45 +5360,109 @@ void ShaderEditor::Render() it = filters_.erase(it); } - // get current clone source - CloneSource *c = nullptr; - Source *s = Mixer::manager().currentSource(); - if (s != nullptr) { - // there is a current source - c = dynamic_cast(s); - 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(); + // if compiling, cannot change source nor do anything else + static std::chrono::milliseconds timeout = std::chrono::milliseconds(4); + if (compilation_ != nullptr ) + { + // wait for compilation to return + if (compilation_return_.wait_for(timeout) == std::future_status::ready ) + { + // get message returned from compilation + std::string s = compilation_return_.get(); + + // find reported line numbers "0(nn)" and replace with "line N" + status_ = ""; + std::regex e("0\\([[:digit:]]+\\)"); + std::smatch m; + while (std::regex_search(s, m, e)) { + status_ += m.prefix().str(); + int l = 0; + std::string num = m.str().substr(2, m.length()-2); + if ( BaseToolkit::is_a_number(num, &l)){ + status_ += "line "; + status_ += std::to_string(l-16); + } + s = m.suffix().str(); + } + status_ += s; + + // end compilation promise + delete compilation_; + compilation_ = nullptr; } } + // not compiling + else { - // change editor text only if current changed - if ( current_ != c) { - // switch to another clone - if ( c != nullptr ) { - _editor.SetText(filters_[c].code()); - _editor.SetReadOnly(false); + // get current clone source + CloneSource *c = nullptr; + Source *s = Mixer::manager().currentSource(); + if (s != nullptr) { + // there is a current source + c = dynamic_cast(s); + 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(); + } + else + status_ = "No shader"; } - // cancel edit clone - else { - // get the editor text and remove trailing '\n' - std::string code = _editor.GetText(); - code.back() = '\0'; - // remember this code as buffered for the filter of this source - filters_[current_].setCode( code ); - // cancel editor - _editor.SetText(""); - _editor.SetReadOnly(true); + else + status_ = "-"; + + // change editor text only if current changed + if ( current_ != c) { + // switch to another clone + if ( c != nullptr ) { + _editor.SetText(filters_[c].code()); + _editor.SetReadOnly(false); + status_ = "Ready."; + } + // cancel edit clone + else { + // get the editor text and remove trailing '\n' + std::string code = _editor.GetText(); + code = code.substr(0, code.size() -1); + // remember this code as buffered for the filter of this source + filters_[current_].setCode( code ); + // cancel editor + _editor.SetText(""); + _editor.SetReadOnly(true); + } + // current changed + current_ = c; } - // current changed - current_ = c; + } - // render the editor + // render the window content in mono font ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + + // render status message + ImGui::Text("Status: %s", status_.c_str()); + + // render shader input + if (show_shader_inputs_) { + ImGuiTextBuffer info; + info.append(ImageFilter::getFilterCodeInputs().c_str()); + + // Show info text bloc (multi line, dark background) + ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO ); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::InputTextMultiline("##Info", (char *)info.c_str(), info.size(), ImVec2(-1, 8*ImGui::GetTextLineHeightWithSpacing()), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(2); + ImGui::PopFont(); + } + else + ImGui::Spacing(); + + // render main editor _editor.Render("Shader Editor"); + ImGui::PopFont(); ImGui::End(); diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index e0fddb7..55b958f 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -392,7 +393,12 @@ class ShaderEditor : public WorkspaceWindow { CloneSource *current_; bool current_changed_; + bool show_shader_inputs_; std::map filters_; + std::promise *compilation_; + std::future compilation_return_; + + std::string status_; public: ShaderEditor();