Minimally operational Shader editor

Can edit code in GLSL, syntax highlighted, and compile shader. Compatible with ShaderToy code.
This commit is contained in:
Bruno Herbelin
2022-04-23 01:02:31 +02:00
parent 9d7f0b22f7
commit 77dc563219
8 changed files with 212 additions and 141 deletions

View File

@@ -229,9 +229,9 @@ void CloneSource::setDelay(double second)
}
void CloneSource::setFilter(const ImageFilter &filter)
void CloneSource::setFilter(const ImageFilter &filter, std::promise<std::string> *ret)
{
filter_render_->setFilter(filter);
filter_render_->setFilter(filter, ret);
}
ImageFilter CloneSource::filter() const

View File

@@ -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<std::string> *ret = nullptr);
ImageFilter filter() const;
glm::ivec2 icon() const override;

View File

@@ -31,15 +31,14 @@
#include "ImageFilter.h"
std::string ShaderToyHeaderHelp = "vec3 iResolution; // viewport resolution (in pixels)\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"
"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";
"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<std::string, float> parameters);
void setFilterCode(const std::string &code, std::map<std::string, float> parameters, std::promise<std::string> *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<std::string, float> parameters)
void ImageFilteringShader::setFilterCode(const std::string &code, std::map<std::string, float> parameters, std::promise<std::string> *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<std::
void ImageFilteringShader::copy(ImageFilteringShader const& S)
{
// change the shading code for fragment
code_ = S.code_;
custom_shading_.setShaders("shaders/image.vs", code_);
shader_code_ = S.shader_code_;
custom_shading_.setShaders("shaders/image.vs", shader_code_);
// set the list of uniforms
uniforms_.clear();
@@ -199,58 +197,15 @@ void ImageFilteringShader::accept(Visitor& v)
v.visit(*this);
}
std::string ImageFilter::getFilterCodeInputs()
{
return filterHeaderHelp;
}
std::string filterBloomCode = "float Threshold = 0.2;"
"float Intensity = 1.0;"
"float BlurSize = 4.0;"
"vec4 BlurColor (in vec2 Coord, in sampler2D Tex, in float MipBias)"
"{"
" vec2 TexelSize = MipBias/iChannelResolution[0].xy;"
" vec4 Color = texture(Tex, Coord, MipBias);"
" Color += texture(Tex, Coord + vec2(TexelSize.x,0.0), MipBias);"
" Color += texture(Tex, Coord + vec2(-TexelSize.x,0.0), MipBias);"
" Color += texture(Tex, Coord + vec2(0.0,TexelSize.y), MipBias);"
" Color += texture(Tex, Coord + vec2(0.0,-TexelSize.y), MipBias);"
" Color += texture(Tex, Coord + vec2(TexelSize.x,TexelSize.y), MipBias);"
" Color += texture(Tex, Coord + vec2(-TexelSize.x,TexelSize.y), MipBias);"
" Color += texture(Tex, Coord + vec2(TexelSize.x,-TexelSize.y), MipBias);"
" Color += texture(Tex, Coord + vec2(-TexelSize.x,-TexelSize.y), MipBias);"
" return Color/9.0;"
"}"
"void mainImage( out vec4 fragColor, in vec2 fragCoord )"
"{"
" vec2 uv = (fragCoord.xy/iResolution.xy);"
" vec4 Color = texture(iChannel0, uv);"
" vec4 Highlight = clamp(BlurColor(uv, iChannel0, BlurSize)-Threshold,0.0,1.0)*1.0/(1.0-Threshold);"
" fragColor = 1.0-(1.0-Color)*(1.0-Highlight*Intensity);"
"}";
std::string montecarloCode = "#define ITER 32\n"
"#define SIZE 100\n"
"void srand(vec2 a, out float r) {r=sin(dot(a,vec2(1233.224,1743.335)));}\n"
"float rand(inout float r) { r=fract(3712.65*r+0.61432); return (r-0.5)*2.0;}\n"
"void mainImage( out vec4 fragColor, in vec2 fragCoord ) {\n"
"vec2 texcoord = fragCoord.xy / iResolution.xy;\n"
"float p = (SIZE * 0.6 + 1.0)/textureSize(iChannel0, 0).y * 0.98;"
"vec4 c=vec4(0.0);"
"float r;"
"srand(vec2(texcoord), r);"
"vec2 rv;"
"for(int i=0;i<ITER;i++) {"
"rv.x=rand(r);"
"rv.y=rand(r);"
"c+=texture(iChannel0,texcoord+rv*p)/float(ITER);"
"}"
"fragColor = c;\n"
"}";
std::map< std::string, float > 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->code_ != other.code_)
return true;
}
if (this == &other || this->code_ == other.code_)
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<std::string> *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

View File

@@ -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<std::string> *ret = nullptr);
// get the code of the filter
inline ImageFilter filter() const { return filter_; }

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
@@ -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<std::string> *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()

View File

@@ -1,6 +1,7 @@
#ifndef __SHADER_H_
#define __SHADER_H_
#include <future>
#include <string>
#include <vector>
#include <glm/glm.hpp>
@@ -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<std::string> *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<std::string> *promise_;
static ShadingProgram *currentProgram_;
};

View File

@@ -18,16 +18,15 @@
**/
#include <iostream>
#include <cstring>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <sstream>
#include <cstring>
#include <thread>
#include <algorithm>
#include <array>
#include <map>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <sstream>
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<std::string>();
current_->setFilter( filters_[current_], compilation_ );
compilation_return_ = compilation_->get_future();
// inform status
status_ = "Building...";
}
}
ImGui::EndMenuBar();
@@ -5326,6 +5360,40 @@ void ShaderEditor::Render()
it = filters_.erase(it);
}
// 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 {
// get current clone source
CloneSource *c = nullptr;
Source *s = Mixer::manager().currentSource();
@@ -5338,7 +5406,11 @@ void ShaderEditor::Render()
// the current clone was not registered
filters_[c] = c->filter();
}
else
status_ = "No shader";
}
else
status_ = "-";
// change editor text only if current changed
if ( current_ != c) {
@@ -5346,12 +5418,13 @@ void ShaderEditor::Render()
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.back() = '\0';
code = code.substr(0, code.size() -1);
// remember this code as buffered for the filter of this source
filters_[current_].setCode( code );
// cancel editor
@@ -5362,9 +5435,34 @@ void ShaderEditor::Render()
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();

View File

@@ -4,6 +4,7 @@
#include <string>
#include <array>
#include <list>
#include <future>
#include <gst/gstutils.h>
@@ -392,7 +393,12 @@ class ShaderEditor : public WorkspaceWindow
{
CloneSource *current_;
bool current_changed_;
bool show_shader_inputs_;
std::map<CloneSource *, ImageFilter> filters_;
std::promise<std::string> *compilation_;
std::future<std::string> compilation_return_;
std::string status_;
public:
ShaderEditor();