BugFix Shadertoy ImageFilter

This commit is contained in:
Bruno Herbelin
2022-08-08 21:17:16 +02:00
parent 12f8c75c2d
commit a13b0d5d91
6 changed files with 132 additions and 130 deletions

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
**/ **/
#include <ctime> #include <ctime>
#include <algorithm>
#include <glib.h> #include <glib.h>
#include <glm/gtc/matrix_access.hpp> #include <glm/gtc/matrix_access.hpp>
@@ -48,7 +49,6 @@ std::string fragmentHeader = "#version 330 core\n"
"uniform vec4 iDate;\n" "uniform vec4 iDate;\n"
"uniform vec4 iMouse;\n"; "uniform vec4 iMouse;\n";
// Filter code starts at line 16 :
std::string filterDefault = "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n" std::string filterDefault = "void mainImage( out vec4 fragColor, in vec2 fragCoord )\n"
"{\n" "{\n"
" vec2 uv = fragCoord.xy / iResolution.xy;\n" " vec2 uv = fragCoord.xy / iResolution.xy;\n"
@@ -76,11 +76,6 @@ std::list< FilteringProgram > FilteringProgram::presets = {
FilteringProgram("Logo", "shaders/filters/logo.glsl", "", { }) FilteringProgram("Logo", "shaders/filters/logo.glsl", "", { })
}; };
int FilteringProgram::getFilterHeaderNumlines()
{
return 14;
}
std::string FilteringProgram::getFilterCodeInputs() std::string FilteringProgram::getFilterCodeInputs()
{ {
static std::string filterHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\n" static std::string filterHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\n"
@@ -279,10 +274,15 @@ void ImageFilteringShader::setCode(const std::string &code, std::promise<std::st
if (code != code_) if (code != code_)
{ {
code_ = code; code_ = code;
// ensure code to compile is correct
if (code_.empty()) if (code_.empty())
code_ = filterDefault; code_ = filterDefault;
// shader is composed of a header, the given code and a footer
shader_code_ = fragmentHeader + code_ + fragmentFooter; shader_code_ = fragmentHeader + code_ + fragmentFooter;
custom_shading_.setShaders("shaders/image.vs", shader_code_, ret); // shift line numbers by number of lines in header
std::string::difference_type n = std::count(fragmentHeader.begin(), fragmentHeader.end(), '\n');
// launch build
custom_shading_.setShaders("shaders/image.vs", shader_code_, (int)n, ret);
} }
else if (ret != nullptr) { else if (ret != nullptr) {
ret->set_value("No change."); ret->set_value("No change.");

View File

@@ -57,7 +57,6 @@ public:
inline void setParameter(const std::string &p, float value) { parameters_[p] = value; } inline void setParameter(const std::string &p, float value) { parameters_[p] = value; }
// globals // globals
static int getFilterHeaderNumlines();
static std::string getFilterCodeInputs(); static std::string getFilterCodeInputs();
static std::string getFilterCodeDefault(); static std::string getFilterCodeDefault();
static std::list< FilteringProgram > presets; static std::list< FilteringProgram > presets;

View File

@@ -20,6 +20,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <regex>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
@@ -77,16 +78,17 @@ GLenum blending_destination_function[9] = {GL_ONE_MINUS_SRC_ALPHA,// normal
GL_ZERO}; GL_ZERO};
ShadingProgram::ShadingProgram(const std::string& vertex, const std::string& fragment) : ShadingProgram::ShadingProgram(const std::string& vertex, const std::string& fragment) :
id_(0), need_compile_(true), vertex_(vertex), fragment_(fragment), promise_(nullptr) id_(0), need_compile_(true), lineshift_(0), vertex_(vertex), fragment_(fragment), promise_(nullptr)
{ {
} }
void ShadingProgram::setShaders(const std::string& vertex, const std::string& fragment, std::promise<std::string> *prom) void ShadingProgram::setShaders(const std::string& vertex, const std::string& fragment, int lineshift, std::promise<std::string> *prom)
{ {
vertex_ = vertex; vertex_ = vertex;
fragment_ = fragment; fragment_ = fragment;
need_compile_ = true; lineshift_ = lineshift;
promise_ = prom; promise_ = prom;
need_compile_ = true;
} }
void ShadingProgram::compile() void ShadingProgram::compile()
@@ -160,12 +162,39 @@ void ShadingProgram::compile()
} }
} }
std::string message;
// if a lineshift was given, fix the line numbers in info log string
if (lineshift_ > 0) {
std::string s(infoLog);
std::smatch m;
#ifdef APPLE
std::regex e("0\\:[[:digit:]]+");
#else
std::regex e("0\\([[:digit:]]+\\)");
#endif
while (std::regex_search(s, m, e)) {
message += m.prefix().str();
int l = 0;
std::string num = m.str().substr(2, m.length()-2);
if ( BaseToolkit::is_a_number(num, &l)){
message += "line ";
message += std::to_string(l - lineshift_);
}
s = m.suffix().str();
}
message += s;
}
// default is to use info log message
else
message = std::string(infoLog);
// always fulfill a promise // always fulfill a promise
if (promise_) if (promise_)
promise_->set_value( success ? "Ok" : "Error:\n" + std::string(infoLog) ); promise_->set_value( success ? "Ok" : "Error\n" + message );
// if not asked to return a promise, inform user through logs // if not asked to return a promise, inform user through logs
else if (!success) else if (!success)
Log::Warning("Error compiling Vertex ShadingProgram:\n%s", infoLog); Log::Warning("Error compiling Vertex ShadingProgram:\n%s", message.c_str());
// do not compile indefinitely // do not compile indefinitely
need_compile_ = false; need_compile_ = false;

View File

@@ -18,7 +18,7 @@ public:
// Update GLSL Program with vertex and fragment program // Update GLSL Program with vertex and fragment program
// If a promise is given, it is filled during compilation with the compilation log. // 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<std::string> *prom = nullptr); void setShaders(const std::string& vertex, const std::string& fragment, int lineshift = 0, std::promise<std::string> *prom = nullptr);
void use(); void use();
void compile(); void compile();
@@ -32,6 +32,7 @@ public:
private: private:
unsigned int id_; unsigned int id_;
bool need_compile_; bool need_compile_;
int lineshift_;
std::string vertex_; std::string vertex_;
std::string fragment_; std::string fragment_;
std::promise<std::string> *promise_; std::promise<std::string> *promise_;

View File

@@ -1717,7 +1717,7 @@ void HelperToolbox::Render()
ImGui::Text ("Render a session (*.mix) as a source."); ImGui::Text ("Render a session (*.mix) as a source.");
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Separator(); ImGui::Separator();
ImGui::Text(ICON_FA_SORT_NUMERIC_DOWN); ImGui::NextColumn(); ImGui::Text(ICON_FA_IMAGES); ImGui::NextColumn();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Sequence");ImGui::PopFont(); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Sequence");ImGui::PopFont();
ImGui::NextColumn(); ImGui::NextColumn();
ImGuiToolkit::Icon(ICON_SOURCE_SEQUENCE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Sequence"); ImGui::NextColumn(); ImGuiToolkit::Icon(ICON_SOURCE_SEQUENCE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Sequence"); ImGui::NextColumn();
@@ -5487,8 +5487,7 @@ void InputMappingInterface::Render()
/// SHADER EDITOR /// SHADER EDITOR
/// ///
/// ///
ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr), show_shader_inputs_(false)
current_changed_(true), show_shader_inputs_(false)
{ {
auto lang = TextEditor::LanguageDefinition::GLSL(); auto lang = TextEditor::LanguageDefinition::GLSL();
@@ -5535,6 +5534,7 @@ ShaderEditor::ShaderEditor() : WorkspaceWindow("Shader"), current_(nullptr),
_editor.SetHandleKeyboardInputs(true); _editor.SetHandleKeyboardInputs(true);
_editor.SetShowWhitespaces(false); _editor.SetShowWhitespaces(false);
_editor.SetText(""); _editor.SetText("");
_editor.SetReadOnly(true);
// status // status
status_ = "-"; status_ = "-";
@@ -5562,13 +5562,25 @@ bool ShaderEditor::Visible() const
); );
} }
void ShaderEditor::setVisible(CloneSource *cs) void ShaderEditor::BuildShader()
{ {
if ( cs != nullptr ) { // if the UI has a current clone, and ref to code for current clone is valid
FrameBufferFilter *f = cs->filter(); if (current_ != nullptr && filters_.find(current_) != filters_.end()) {
// if the filter is an Image Filter
if (f && f->type() == FrameBufferFilter::FILTER_IMAGE ) // set the code of the current filter
setVisible(true); filters_[current_].setCode( { _editor.GetText(), "" } );
// filter changed, cannot be named as before
filters_[current_].setName("Custom");
// change the filter of the current image filter
// => this triggers compilation of shader
compilation_ = new std::promise<std::string>();
current_->setProgram( filters_[current_], compilation_ );
compilation_return_ = compilation_->get_future();
// inform status
status_ = "Building...";
} }
} }
@@ -5583,9 +5595,6 @@ void ShaderEditor::Render()
return; return;
} }
bool ro = _editor.IsReadOnly();
bool ws = _editor.IsShowingWhitespaces();
// menu (no title bar) // menu (no title bar)
if (ImGui::BeginMenuBar()) if (ImGui::BeginMenuBar())
{ {
@@ -5594,6 +5603,7 @@ void ShaderEditor::Render()
Settings::application.widget.shader_editor = false; Settings::application.widget.shader_editor = false;
if (ImGui::BeginMenu(IMGUI_TITLE_SHADEREDITOR)) if (ImGui::BeginMenu(IMGUI_TITLE_SHADEREDITOR))
{ {
// reload code from GPU
if (ImGui::MenuItem( ICON_FA_SYNC " Reload", nullptr, nullptr, current_ != nullptr)) { if (ImGui::MenuItem( ICON_FA_SYNC " Reload", nullptr, nullptr, current_ != nullptr)) {
// force reload // force reload
if ( current_ != nullptr ) if ( current_ != nullptr )
@@ -5607,35 +5617,30 @@ void ShaderEditor::Render()
for (auto p = FilteringProgram::presets.begin(); p != FilteringProgram::presets.end(); ++p){ for (auto p = FilteringProgram::presets.begin(); p != FilteringProgram::presets.end(); ++p){
if (current_ != nullptr && ImGui::MenuItem( p->name().c_str() )) { if (current_ != nullptr && ImGui::MenuItem( p->name().c_str() )) {
ImageFilter *i = dynamic_cast<ImageFilter *>( current_->filter() ); // change the filter of the current image filter
// if we can access the code of inside the image filter // => this triggers compilation of shader
if (i) { compilation_ = new std::promise<std::string>();
// change the filter of the current image filter current_->setProgram( *p, compilation_ );
// => this triggers compilation of shader compilation_return_ = compilation_->get_future();
compilation_ = new std::promise<std::string>(); // inform status
i->setProgram( *p, compilation_ ); status_ = "Building...";
compilation_return_ = compilation_->get_future(); // force reload
// inform status if ( current_ != nullptr )
status_ = "Building..."; filters_.erase(current_);
// force reload current_ = nullptr;
if ( current_ != nullptr )
filters_.erase(current_);
current_ = nullptr;
}
} }
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::MenuItem( ICON_FA_EXTERNAL_LINK_ALT " Browse shadertoy.com")) { // Open browser to shadertoy website
if (ImGui::MenuItem( ICON_FA_EXTERNAL_LINK_ALT " Browse shadertoy.com"))
SystemToolkit::open("https://www.shadertoy.com/"); SystemToolkit::open("https://www.shadertoy.com/");
}
// Enable/Disable editor options // Enable/Disable editor options
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem( ICON_FA_UNDERLINE " Show Shader Inputs", nullptr, &show_shader_inputs_); ImGui::MenuItem( ICON_FA_UNDERLINE " Show Shader Inputs", nullptr, &show_shader_inputs_);
bool ws = _editor.IsShowingWhitespaces();
if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Show whitespace", nullptr, &ws)) if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Show whitespace", nullptr, &ws))
_editor.SetShowWhitespaces(ws); _editor.SetShowWhitespaces(ws);
@@ -5656,6 +5661,7 @@ void ShaderEditor::Render()
} }
// Edit menu // Edit menu
bool ro = _editor.IsReadOnly();
if (ImGui::BeginMenu( "Edit", current_ != nullptr ) ) { if (ImGui::BeginMenu( "Edit", current_ != nullptr ) ) {
if (ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO, nullptr, !ro && _editor.CanUndo())) if (ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO, nullptr, !ro && _editor.CanUndo()))
@@ -5677,103 +5683,59 @@ void ShaderEditor::Render()
} }
// Build action menu // Build action menu
if (ImGui::MenuItem( ICON_FA_HAMMER " Build", nullptr, nullptr, current_ != nullptr )) { if (ImGui::MenuItem( ICON_FA_HAMMER " Build", CTRL_MOD "B", nullptr, current_ != nullptr ))
BuildShader();
// the UI has ref to code for this clone
if (current_ != nullptr && filters_.find(current_) != filters_.end()) {
ImageFilter *i = dynamic_cast<ImageFilter *>( 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(), "" } );
// filter changed, cannot be named as before
filters_[current_].setName("Custom");
// change the filter of the current image filter
// => this triggers compilation of shader
compilation_ = new std::promise<std::string>();
i->setProgram( filters_[current_], compilation_ );
compilation_return_ = compilation_->get_future();
// inform status
status_ = "Building...";
}
}
}
ImGui::EndMenuBar(); ImGui::EndMenuBar();
} }
// garbage collection of code_ // garbage collection
for (auto it = filters_.begin(); it != filters_.end(); ) { if ( Mixer::manager().session()->numSources() < 1 )
// keep only if the source exists in the session {
if ( Mixer::manager().session()->find( it->first ) != Mixer::manager().session()->end() ) filters_.clear();
++it; current_ = nullptr;
else
it = filters_.erase(it);
} }
// if compiling, cannot change source nor do anything else // if compiling, cannot change source nor do anything else
static std::chrono::milliseconds timeout = std::chrono::milliseconds(4); static std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
if (compilation_ != nullptr ) if (compilation_ != nullptr && compilation_return_.wait_for(timeout) == std::future_status::ready )
{ {
// wait for compilation to return // get message returned from compilation
if (compilation_return_.wait_for(timeout) == std::future_status::ready ) status_ = compilation_return_.get();
{
// get message returned from compilation
std::string s = compilation_return_.get();
// find reported line numbers "0:nn" and replace with "line N" // end compilation promise
status_ = ""; delete compilation_;
std::regex e("0\\:[[:digit:]]+"); compilation_ = nullptr;
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 - FilteringProgram::getFilterHeaderNumlines());
status_ += " ";
}
s = m.suffix().str();
}
status_ += s;
// end compilation promise
delete compilation_;
compilation_ = nullptr;
}
} }
// not compiling // not compiling
else { else {
ImageFilter *i = nullptr;
// get current clone source // get current clone source
CloneSource *c = nullptr;
Source *s = Mixer::manager().currentSource(); Source *s = Mixer::manager().currentSource();
// if there is a current source // if there is a current source
if (s != nullptr) { if (s != nullptr) {
c = dynamic_cast<CloneSource *>(s); CloneSource *c = dynamic_cast<CloneSource *>(s);
// if the current source is a clone // if the current source is a clone
if ( c != nullptr ) { if ( c != nullptr ) {
FrameBufferFilter *f = c->filter(); FrameBufferFilter *f = c->filter();
// if the filter seems to be an Image Filter // if the filter seems to be an Image Filter
if (f && f->type() == FrameBufferFilter::FILTER_IMAGE ) { if (f != nullptr && f->type() == FrameBufferFilter::FILTER_IMAGE ) {
ImageFilter *i = dynamic_cast<ImageFilter *>(f); i = dynamic_cast<ImageFilter *>(f);
// if we can access the code of the filter // if we can access the code of the filter
if (i) { if (i != nullptr) {
// if the current clone was not already registered // if the current clone was not already registered
if ( filters_.find(c) == filters_.end() ) if ( filters_.find(i) == filters_.end() )
// remember code for this clone // remember code for this clone
filters_[c] = i->program(); filters_[i] = i->program();
}
else {
filters_.erase(i);
i = nullptr;
} }
} }
else { else
status_ = "-"; status_ = "-";
c = nullptr;
}
} }
else else
status_ = "-"; status_ = "-";
@@ -5782,27 +5744,31 @@ void ShaderEditor::Render()
status_ = "-"; status_ = "-";
// change editor text only if current changed // change editor text only if current changed
if ( current_ != c) { if ( current_ != i)
// switch to another clone {
if ( c != nullptr ) { // get the editor text and remove trailing '\n'
_editor.SetText( filters_[c].code().first ); 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, "" } );
// if switch to another shader code
if ( i != nullptr ) {
// change editor
_editor.SetText( filters_[i].code().first );
_editor.SetReadOnly(false); _editor.SetReadOnly(false);
status_ = "Ready."; status_ = "Ready.";
} }
// cancel edit clone // cancel edit clone
else { 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 // cancel editor
_editor.SetText(""); _editor.SetText("");
_editor.SetReadOnly(true); _editor.SetReadOnly(true);
status_ = "-";
} }
// current changed // current changed
current_ = c; current_ = i;
} }
} }
@@ -5833,6 +5799,14 @@ void ShaderEditor::Render()
else else
ImGui::Spacing(); ImGui::Spacing();
// special case for 'CTRL + B' keyboard shortcut
// the TextEditor captures keyboard focus from the main imgui context
// so UserInterface::handleKeyboard cannot capture this event:
// reading key press before render bypasses this problem
const ImGuiIO& io = ImGui::GetIO();
if (io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_B))
BuildShader();
// render main editor // render main editor
_editor.Render("Shader Editor"); _editor.Render("Shader Editor");

View File

@@ -398,13 +398,12 @@ public:
class ShaderEditor : public WorkspaceWindow class ShaderEditor : public WorkspaceWindow
{ {
CloneSource *current_; ImageFilter *current_;
bool current_changed_; std::map<ImageFilter *, FilteringProgram> filters_;
bool show_shader_inputs_;
std::map<CloneSource *, FilteringProgram> filters_;
std::promise<std::string> *compilation_; std::promise<std::string> *compilation_;
std::future<std::string> compilation_return_; std::future<std::string> compilation_return_;
bool show_shader_inputs_;
std::string status_; std::string status_;
public: public:
@@ -413,7 +412,7 @@ public:
void Render(); void Render();
void setVisible(bool on); void setVisible(bool on);
void setVisible(CloneSource *cs); void BuildShader();
// from WorkspaceWindow // from WorkspaceWindow
bool Visible() const override; bool Visible() const override;