mirror of
https://github.com/Almamu/linux-wallpaperengine.git
synced 2025-07-15 13:52:23 +08:00
388 lines
17 KiB
C++
388 lines
17 KiB
C++
#include "common.h"
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
// shader compiler
|
|
#include <WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantFloat.h>
|
|
#include <WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantInteger.h>
|
|
#include <WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantVector4.h>
|
|
#include <WallpaperEngine/Render/Shaders/CCompiler.h>
|
|
#include <regex>
|
|
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h"
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableFloat.h"
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableInteger.h"
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector2.h"
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector3.h"
|
|
#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector4.h"
|
|
|
|
#include "CGLSLContext.h"
|
|
#include "WallpaperEngine/Assets/CAssetLoadException.h"
|
|
|
|
using namespace WallpaperEngine::Core;
|
|
using namespace WallpaperEngine::Assets;
|
|
|
|
namespace WallpaperEngine::Render::Shaders {
|
|
CCompiler::CCompiler (CContainer* container, std::string filename, CGLSLContext::ShaderType type, std::map<std::string, int>* combos,
|
|
std::map<std::string, bool>* foundCombos, const std::vector<std::string>& textures,
|
|
const std::map<std::string, CShaderConstant*>& constants) :
|
|
m_combos (combos),
|
|
m_foundCombos (foundCombos),
|
|
m_passTextures (textures),
|
|
m_type (type),
|
|
m_file (std::move (filename)),
|
|
m_constants (constants),
|
|
m_container (container) {
|
|
if (type == CGLSLContext::ShaderType_Vertex)
|
|
this->m_content = this->m_container->readVertexShader (this->m_file);
|
|
else if (type == CGLSLContext::ShaderType_Pixel)
|
|
this->m_content = this->m_container->readFragmentShader (this->m_file);
|
|
else if (type == CGLSLContext::ShaderType_Include)
|
|
sLog.exception ("Include shaders should never be compiled, they're part of a bigger shader: ", this->m_file);
|
|
|
|
// clone the combos into the baseCombos to keep track of values that must be embedded no matter what
|
|
for (const auto& [name, value] : *this->m_combos)
|
|
this->m_baseCombos.insert (std::make_pair (name, value));
|
|
}
|
|
|
|
std::string CCompiler::lookupShaderFile (const std::string& filename) {
|
|
// TODO:
|
|
// includes are not compiled, lookup the file and return the contents
|
|
// there might be situations where an #include is part of a comment
|
|
// instead of trying to figure out if an #include is part of that
|
|
// just try to load it
|
|
// if nothing is found, nothing is really lost
|
|
// but if something is found, depending on the type of comment and the contents
|
|
// the included file could break the shader, we'll need to perform some more
|
|
// checks here at some point, but for now should be enough
|
|
try {
|
|
return this->m_container->readIncludeShader (filename);
|
|
} catch (CAssetLoadException&) {
|
|
sLog.error ("Cannot find include file ", filename, " in shader ", this->m_file, ". Using empty content.");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::string& CCompiler::getCompiled () {
|
|
return this->m_compiledContent;
|
|
}
|
|
|
|
void CCompiler::compile () {
|
|
// reset include contents as the compilation requires this to be re-processed
|
|
this->m_includeContent = "";
|
|
std::string precompile = "#version 330\n"
|
|
"// ======================================================\n"
|
|
"// Processed shader " +
|
|
this->m_file +
|
|
"\n"
|
|
"// ======================================================\n"
|
|
"precision highp float;\n"
|
|
"#define mul(x, y) ((y) * (x))\n"
|
|
"#define max(x, y) max (y, x)\n"
|
|
"#define lerp mix\n"
|
|
"#define frac fract\n"
|
|
"#define CAST2(x) (vec2(x))\n"
|
|
"#define CAST3(x) (vec3(x))\n"
|
|
"#define CAST4(x) (vec4(x))\n"
|
|
"#define CAST3X3(x) (mat3(x))\n"
|
|
"#define saturate(x) (clamp(x, 0.0, 1.0))\n"
|
|
"#define texSample2D texture\n"
|
|
"#define texSample2DLod textureLod\n"
|
|
"#define log10(x) log2(x) * 0.301029995663981\n"
|
|
"#define atan2 atan\n"
|
|
"#define fmod(x, y) ((x)-(y)*trunc((x)/(y)))\n"
|
|
"#define ddx dFdx\n"
|
|
"#define ddy(x) dFdy(-(x))\n"
|
|
"#define GLSL 1\n\n";
|
|
|
|
if (this->m_type == CGLSLContext::ShaderType_Vertex) {
|
|
precompile += "#define attribute in\n"
|
|
"#define varying out\n";
|
|
} else {
|
|
precompile += "out vec4 out_FragColor;\n"
|
|
"#define varying in\n";
|
|
}
|
|
// searches for the combos available and adds the defines required
|
|
|
|
// go line by line in the shader content
|
|
size_t start = 0, end = 0;
|
|
while ((end = this->m_content.find ('\n', start)) != std::string::npos) {
|
|
// Extract a line from the string
|
|
std::string line = this->m_content.substr (start, end - start);
|
|
size_t combo = line.find("// [COMBO] ");
|
|
size_t uniform = line.find("uniform ");
|
|
size_t comment = line.find("// ");
|
|
size_t semicolon = line.find(';');
|
|
|
|
if (combo != std::string::npos) {
|
|
this->parseComboConfiguration (line.substr(combo + strlen("// [COMBO] ")), 0);
|
|
} else if (uniform != std::string::npos && comment != std::string::npos && semicolon != std::string::npos) {
|
|
// uniforms with comments should never have a value assigned, use this fact to detect the required parts
|
|
size_t last_space = line.find_last_of (' ', semicolon);
|
|
|
|
if (last_space != std::string::npos) {
|
|
size_t previous_space = line.find_last_of (' ', last_space - 1);
|
|
|
|
if (previous_space != std::string::npos) {
|
|
// extract type and name
|
|
std::string type = line.substr (previous_space + 1, last_space - previous_space - 1);
|
|
std::string name = line.substr (last_space + 1, semicolon - last_space - 1);
|
|
std::string json = line.substr (comment + 2);
|
|
|
|
this->parseParameterConfiguration (type, name, json);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Move to the next line
|
|
start = end + 1;
|
|
}
|
|
|
|
// add all the defines we have for now
|
|
for (const auto& [name, value] : *this->m_foundCombos) {
|
|
// find the right value for the combo in the combos map
|
|
auto combo = this->m_combos->find (name);
|
|
|
|
if (combo == this->m_combos->end ())
|
|
continue;
|
|
|
|
precompile += "#define " + name + " " + std::to_string (combo->second) + "\n";
|
|
}
|
|
|
|
// add base combos that come from the pass change that MUST be added
|
|
for (const auto& [name, value] : this->m_baseCombos) {
|
|
auto alreadyFound = this->m_foundCombos->find (name);
|
|
|
|
if (alreadyFound != this->m_foundCombos->end ())
|
|
continue;
|
|
|
|
precompile += "#define " + name + " " + std::to_string (value) + "\n";
|
|
}
|
|
|
|
precompile += this->m_content;
|
|
|
|
// reset end so we start from the beginning
|
|
end = 0;
|
|
|
|
// then apply includes in-place
|
|
while((start = precompile.find("#include", end)) != std::string::npos) {
|
|
// TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
|
|
size_t quoteStart = precompile.find_first_of ('"', start) + 1;
|
|
size_t quoteEnd = precompile.find_first_of('"', quoteStart);
|
|
std::string filename = precompile.substr(quoteStart, quoteEnd - quoteStart);
|
|
|
|
std::string content = "// begin of include from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
content += this->lookupShaderFile (filename);
|
|
content += "\n// end of included from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
|
|
// replace the first two letters with a comment so the filelength doesn't change
|
|
precompile = precompile.replace (start, 1 + quoteEnd - start, content);
|
|
//precompile = precompile.replace(start, 2, "//");
|
|
//this->m_includeContent += content;
|
|
|
|
// go to the end of the line
|
|
end = start;
|
|
}
|
|
|
|
// include content might have more includes, so also handle those
|
|
/*end = 0;
|
|
|
|
// then apply includes in-place
|
|
while((start = this->m_includeContent.find("#include", end)) != std::string::npos) {
|
|
size_t lineEnd = this->m_includeContent.find_first_of ('\n', start);
|
|
// TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
|
|
size_t quoteStart = this->m_includeContent.find_first_of ('"', start) + 1;
|
|
size_t quoteEnd = this->m_includeContent.find_first_of('"', quoteStart);
|
|
std::string filename = this->m_includeContent.substr(quoteStart, quoteEnd - quoteStart);
|
|
std::string content = this->lookupShaderFile (filename);
|
|
|
|
// file contents ready, replace things
|
|
this->m_includeContent = this->m_includeContent.replace (start, lineEnd - start,
|
|
"// begin of include from file " + filename + "\n" +
|
|
content +
|
|
"\n// end of included from file " + filename + "\n"
|
|
);
|
|
|
|
// go back to the beginning of the line to properly continue detecting things
|
|
end = start;
|
|
}
|
|
|
|
end = 0;
|
|
|
|
// finally, try to place the include contents before the main function
|
|
while ((start = precompile.find (" main", end)) != std::string::npos) {
|
|
char value = precompile.at(start + 5);
|
|
|
|
end = start + 5;
|
|
|
|
if (value != ' ' && value != '(') {
|
|
continue;
|
|
}
|
|
|
|
// find the beginning of the line, inject include content and call it a day
|
|
size_t previousLine = precompile.rfind ('\n', start);
|
|
// finally insert it there
|
|
precompile.insert (previousLine + 1, this->m_includeContent + '\n');
|
|
// keep the iterator after the found function to prevent a loop
|
|
end = end + this->m_includeContent.length () + 1;
|
|
}*/
|
|
|
|
// content should be ready, finally ask glslang to compile the shader
|
|
this->m_compiledContent = CGLSLContext::get().toGlsl (precompile, this->m_type);
|
|
}
|
|
|
|
void CCompiler::parseComboConfiguration (const std::string& content, int defaultValue) {
|
|
json data = json::parse (content);
|
|
const auto combo = jsonFindRequired (data, "combo", "cannot parse combo information");
|
|
const auto type = data.find ("type");
|
|
const auto defvalue = data.find ("default");
|
|
|
|
// check the combos
|
|
const auto entry = this->m_combos->find (combo->get<std::string> ());
|
|
|
|
// add the combo to the found list
|
|
this->m_foundCombos->insert (std::make_pair<std::string, int> (*combo, true));
|
|
|
|
// if the combo was not found in the predefined values this means that the default value in the JSON data can be
|
|
// used so only define the ones that are not already defined
|
|
if (entry == this->m_combos->end ()) {
|
|
if (type != data.end ())
|
|
sLog.error ("Resorting to default value as type ", *type, " is unknown");
|
|
|
|
// if no combo is defined just load the default settings
|
|
if (defvalue == data.end ()) {
|
|
// TODO: PROPERLY SUPPORT EMPTY COMBOS
|
|
this->m_combos->insert (std::make_pair<std::string, int> (*combo, (int) defaultValue));
|
|
} else if (defvalue->is_number_float ()) {
|
|
sLog.exception ("float combos are not supported in shader ", this->m_file, ". ", *combo);
|
|
} else if (defvalue->is_number_integer ()) {
|
|
this->m_combos->insert (std::make_pair<std::string, int> (*combo, defvalue->get<int> ()));
|
|
} else if (defvalue->is_string ()) {
|
|
sLog.exception ("string combos are not supported in shader ", this->m_file, ". ", *combo);
|
|
} else {
|
|
sLog.exception ("cannot parse combo information ", *combo, ". unknown type for ", defvalue->dump ());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCompiler::parseParameterConfiguration (const std::string& type, const std::string& name,
|
|
const std::string& content) {
|
|
json data = json::parse (content);
|
|
const auto material = data.find ("material");
|
|
const auto defvalue = data.find ("default");
|
|
// auto range = data.find ("range");
|
|
const auto combo = data.find ("combo");
|
|
|
|
// this is not a real parameter
|
|
auto constant = this->m_constants.end ();
|
|
|
|
if (material != data.end ())
|
|
constant = this->m_constants.find (*material);
|
|
|
|
if (constant == this->m_constants.end () && defvalue == data.end ()) {
|
|
if (type != "sampler2D")
|
|
sLog.exception ("Cannot parse parameter data for ", name, " in shader ", this->m_file);
|
|
}
|
|
|
|
Variables::CShaderVariable* parameter = nullptr;
|
|
|
|
// TODO: SUPPORT VALUES FOR ALL THESE TYPES
|
|
if (type == "vec4") {
|
|
parameter = new Variables::CShaderVariableVector4 (
|
|
constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector4 (*defvalue)
|
|
: *constant->second->as<CShaderConstantVector4> ()->getValue ());
|
|
} else if (type == "vec3") {
|
|
parameter = new Variables::CShaderVariableVector3 (
|
|
constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector3 (*defvalue)
|
|
: *constant->second->as<CShaderConstantVector4> ()->getValue ());
|
|
} else if (type == "vec2") {
|
|
parameter = new Variables::CShaderVariableVector2 (WallpaperEngine::Core::aToVector2 (*defvalue));
|
|
} else if (type == "float") {
|
|
float value = 0;
|
|
|
|
if (constant == this->m_constants.end ())
|
|
value = defvalue->get<float> ();
|
|
else if (constant->second->is<CShaderConstantFloat> ())
|
|
value = *constant->second->as<CShaderConstantFloat> ()->getValue ();
|
|
else if (constant->second->is<CShaderConstantInteger> ())
|
|
value = *constant->second->as<CShaderConstantInteger> ()->getValue ();
|
|
|
|
parameter = new Variables::CShaderVariableFloat (value);
|
|
} else if (type == "int") {
|
|
int value = 0;
|
|
|
|
if (constant == this->m_constants.end ())
|
|
value = defvalue->get<int> ();
|
|
else if (constant->second->is<CShaderConstantFloat> ())
|
|
value = *constant->second->as<CShaderConstantFloat> ()->getValue ();
|
|
else if (constant->second->is<CShaderConstantInteger> ())
|
|
value = *constant->second->as<CShaderConstantInteger> ()->getValue ();
|
|
|
|
parameter = new Variables::CShaderVariableInteger (value);
|
|
} else if (type == "sampler2D" || type == "sampler2DComparison") {
|
|
// samplers can have special requirements, check what sampler we're working with and create definitions
|
|
// if needed
|
|
const auto textureName = data.find ("default");
|
|
// extract the texture number from the name
|
|
const char value = name.at (std::string ("g_Texture").length ());
|
|
// now convert it to integer
|
|
size_t index = value - '0';
|
|
|
|
if (combo != data.end ()) {
|
|
// if the texture exists (and is not null), add to the combo
|
|
if (this->m_passTextures.size () > index &&
|
|
(!this->m_passTextures.at (index).empty () || textureName != data.end ())) {
|
|
// add the new combo to the list
|
|
this->m_combos->insert (std::make_pair<std::string, int> (*combo, 1));
|
|
|
|
// textures linked to combos need to be tracked too
|
|
if (this->m_foundCombos->find (*combo) == this->m_foundCombos->end ())
|
|
this->m_foundCombos->insert (std::make_pair<std::string, bool> (*combo, true));
|
|
}
|
|
}
|
|
|
|
if (textureName != data.end ())
|
|
this->m_textures.insert (std::make_pair (index, *textureName));
|
|
|
|
// samplers are not saved, we can ignore them for now
|
|
return;
|
|
} else {
|
|
sLog.error ("Unknown parameter type: ", type, " for ", name, " in shader ", this->m_file);
|
|
return;
|
|
}
|
|
|
|
if (material != data.end ()) {
|
|
parameter->setIdentifierName (*material);
|
|
parameter->setName (name);
|
|
|
|
this->m_parameters.push_back (parameter);
|
|
}
|
|
}
|
|
|
|
Variables::CShaderVariable* CCompiler::findParameter (const std::string& identifier) {
|
|
for (const auto& cur : this->m_parameters)
|
|
if (cur->getIdentifierName () == identifier)
|
|
return cur;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const std::vector<Variables::CShaderVariable*>& CCompiler::getParameters () const {
|
|
return this->m_parameters;
|
|
}
|
|
|
|
std::map<std::string, int>* CCompiler::getCombos () const {
|
|
return this->m_combos;
|
|
}
|
|
|
|
const std::map<int, std::string>& CCompiler::getTextures () const {
|
|
return this->m_textures;
|
|
}
|
|
} // namespace WallpaperEngine::Render::Shaders
|