mirror of
https://github.com/Almamu/linux-wallpaperengine.git
synced 2025-09-14 05:46:48 +08:00
574 lines
23 KiB
C++
574 lines
23 KiB
C++
#include "CShaderUnit.h"
|
|
|
|
#include <string>
|
|
#include <regex>
|
|
#include <stack>
|
|
#include <utility>
|
|
#include "WallpaperEngine/Logging/CLog.h"
|
|
|
|
#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantFloat.h"
|
|
#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantInteger.h"
|
|
#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantVector2.h"
|
|
#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantVector3.h"
|
|
#include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstantVector4.h"
|
|
|
|
#include "CGLSLContext.h"
|
|
#include "WallpaperEngine/Assets/CAssetLoadException.h"
|
|
#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"
|
|
|
|
#define SHADER_HEADER(filename) "#version 330\n" \
|
|
"// ======================================================\n" \
|
|
"// Processed shader " + \
|
|
filename + \
|
|
"\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";
|
|
#define FRAGMENT_SHADER_DEFINES "out vec4 out_FragColor;\n" \
|
|
"#define varying in\n"
|
|
#define VERTEX_SHADER_DEFINES "#define attribute in\n" \
|
|
"#define varying out\n"
|
|
#define DEFINE_COMBO(name, value) "#define " + name + " " + std::to_string (value) + "\n";
|
|
|
|
using namespace WallpaperEngine::Core;
|
|
using namespace WallpaperEngine::Assets;
|
|
using namespace WallpaperEngine::Render::Shaders;
|
|
|
|
CShaderUnit::CShaderUnit (
|
|
CGLSLContext::UnitType type, std::string file, std::string content, const CContainer& container,
|
|
const ShaderConstantMap& constants, const TextureMap& passTextures, const TextureMap& overrideTextures,
|
|
const ComboMap& combos
|
|
) :
|
|
m_type (type),
|
|
m_link (nullptr),
|
|
m_container (container),
|
|
m_file (std::move (file)),
|
|
m_constants (constants),
|
|
m_content (std::move(content)),
|
|
m_passTextures (passTextures),
|
|
m_overrideTextures (overrideTextures),
|
|
m_combos (combos),
|
|
m_discoveredCombos (),
|
|
m_usedCombos () {
|
|
// pre-process the shader so the units are clear
|
|
this->preprocess ();
|
|
}
|
|
|
|
void CShaderUnit::preprocess () {
|
|
this->m_preprocessed = this->m_content;
|
|
this->m_includes = "";
|
|
|
|
this->preprocessVariables ();
|
|
this->preprocessIncludes ();
|
|
this->preprocessRequires ();
|
|
|
|
// replace gl_FragColor with the equivalent
|
|
std::string from = "gl_FragColor";
|
|
std::string to = "out_FragColor";
|
|
|
|
size_t start_pos = 0;
|
|
while ((start_pos = this->m_preprocessed.find (from, start_pos)) != std::string::npos) {
|
|
this->m_preprocessed.replace (start_pos, from.length (), to);
|
|
start_pos += to.length (); // Handles case where 'to' is a substring of 'from'
|
|
}
|
|
}
|
|
|
|
void CShaderUnit::preprocessVariables () {
|
|
this->m_preprocessed = this->m_content;
|
|
this->m_includes = "";
|
|
|
|
size_t start = 0, end = 0;
|
|
while ((end = this->m_preprocessed.find ('\n', start)) != std::string::npos) {
|
|
// Extract a line from the string
|
|
std::string line = this->m_preprocessed.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 &&
|
|
// this check ensures that the comment is after the semicolon (so it's not a commented-out line)
|
|
// this needs further refining as it's not taking into account block comments
|
|
semicolon < comment) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
void CShaderUnit::preprocessIncludes () {
|
|
size_t start = 0, end = 0;
|
|
// prepare the include content
|
|
while((start = this->m_preprocessed.find("#include", end)) != std::string::npos) {
|
|
// TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
|
|
size_t quoteStart = this->m_preprocessed.find_first_of ('"', start) + 1;
|
|
size_t quoteEnd = this->m_preprocessed.find_first_of('"', quoteStart);
|
|
std::string filename = this->m_preprocessed.substr(quoteStart, quoteEnd - quoteStart);
|
|
|
|
// some includes might not be present
|
|
// and that should not be treated as an error mainly because these could come from
|
|
// commented out content
|
|
std::string content;
|
|
|
|
try {
|
|
content += "// begin of include from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
content += this->m_container.readIncludeShader (filename);
|
|
content += "\n// end of included from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
} catch (CAssetLoadException&) {
|
|
content += "// tried including file ";
|
|
content += filename;
|
|
content += " but was not found\n";
|
|
}
|
|
|
|
// replace the first two letters with a comment so the filelength doesn't change
|
|
this->m_preprocessed = this->m_preprocessed.replace (start, 2, "//");
|
|
|
|
this->m_includes += content;
|
|
|
|
// go to the end of the line
|
|
end = start;
|
|
}
|
|
|
|
// ensure the included files do not include other files
|
|
end = 0;
|
|
|
|
// then apply includes in-place
|
|
while((start = this->m_includes.find("#include", end)) != std::string::npos) {
|
|
size_t lineEnd = this->m_includes.find_first_of ('\n', start);
|
|
// TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED
|
|
size_t quoteStart = this->m_includes.find_first_of ('"', start) + 1;
|
|
size_t quoteEnd = this->m_includes.find_first_of('"', quoteStart);
|
|
std::string filename = this->m_includes.substr(quoteStart, quoteEnd - quoteStart);
|
|
|
|
// some includes might not be present
|
|
// and that should not be treated as an error mainly because these could come from
|
|
// commented out content
|
|
std::string content;
|
|
|
|
try {
|
|
content = "// begin of include from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
content += this->m_container.readIncludeShader (filename);
|
|
content += "\n// end of included from file ";
|
|
content += filename;
|
|
content += "\n";
|
|
} catch (CAssetLoadException&) {
|
|
content = "// tried including file ";
|
|
content += filename;
|
|
content += " but was not found\n";
|
|
}
|
|
|
|
// file contents ready, replace things
|
|
this->m_includes = this->m_includes.replace (start, lineEnd - start,content);
|
|
|
|
// go back to the beginning of the line to properly continue detecting things
|
|
end = start;
|
|
}
|
|
|
|
// search for the main function and add the includes before that for now
|
|
end = 0;
|
|
bool includesAdded = false;
|
|
|
|
// finally, try to place the include contents before the main function
|
|
while ((start = this->m_preprocessed.find (" main", end)) != std::string::npos) {
|
|
char value = this->m_preprocessed.at(start + 5);
|
|
|
|
end = start + 5;
|
|
|
|
if (value != ' ' && value != '(') {
|
|
continue;
|
|
}
|
|
|
|
// main located, search for uniforms and find the latest one available
|
|
size_t lastAttribute = this->m_preprocessed.rfind ("attribute", start);
|
|
size_t lastVarying = this->m_preprocessed.rfind ("varying", start);
|
|
size_t lastUniform = this->m_preprocessed.rfind ("uniform", start);
|
|
size_t latest = lastAttribute;
|
|
|
|
if (latest == std::string::npos) {
|
|
latest = lastVarying;
|
|
} else if (latest < lastVarying && lastVarying != std::string::npos) {
|
|
latest = lastVarying;
|
|
}
|
|
|
|
if (latest == std::string::npos) {
|
|
latest = lastUniform;
|
|
} else if (latest < lastUniform && lastUniform != std::string::npos) {
|
|
latest = lastUniform;
|
|
}
|
|
|
|
if (latest < start) {
|
|
// find the end of the current line
|
|
latest = this->m_preprocessed.find ('\n', latest);
|
|
} else {
|
|
// find the end of the previous line
|
|
latest = this->m_preprocessed.rfind ('\n', start);
|
|
}
|
|
|
|
// update the function start to point to the end of the previous line
|
|
// as this will be used to determine the position of the includes
|
|
start = this->m_preprocessed.rfind ('\n', start);
|
|
|
|
// keeps track of the start and end of ifdefs to look for the right
|
|
// place to put the includes in
|
|
std::stack<size_t> ifdefStack;
|
|
|
|
// start looking for #if and #endif results and add to the stack so we find the start of the current chain of ifdefs
|
|
// and use that as point
|
|
|
|
// for this we'll use regex
|
|
std::regex ifdef (R"((#if|#endif))");
|
|
std::smatch match;
|
|
size_t current = 0;
|
|
|
|
while (std::regex_search (this->m_preprocessed.cbegin () + current, this->m_preprocessed.cend (), match, ifdef)) {
|
|
current += match.position ();
|
|
|
|
// if it's opening an #ifdef keep track of the start of the block
|
|
// and that's it
|
|
if (this->m_preprocessed.substr (current, 3) == "#if") {
|
|
// go to the next character so the regex doesn't match with the same thing again
|
|
ifdefStack.push (current++);
|
|
continue;
|
|
}
|
|
|
|
// go to the next character so the regex doesn't match with the same thing again
|
|
current ++;
|
|
|
|
// most likely a syntax error, but we'll ignore it for now...
|
|
if (ifdefStack.empty ()) {
|
|
continue;
|
|
}
|
|
|
|
size_t stackStart = ifdefStack.top ();
|
|
ifdefStack.pop ();
|
|
|
|
if (latest > stackStart && latest <= current) {
|
|
latest = this->m_preprocessed.find ('\n', current);
|
|
}
|
|
}
|
|
|
|
// no more matches, get the one that happens the earliest
|
|
// TODO: IS THIS GOOD ENOUGH? MAYBE WE SHOULD BE GETTING THE FIRST #IF BLOCK INSTEAD?
|
|
latest = std::min (latest, start);
|
|
|
|
// finally insert it there
|
|
this->m_preprocessed.insert (latest + 1, this->m_includes + '\n');
|
|
includesAdded = true;
|
|
break;
|
|
}
|
|
|
|
if (!includesAdded) {
|
|
sLog.exception ("Could not find where to place includes for shader unit ", this->m_file);
|
|
}
|
|
}
|
|
|
|
void CShaderUnit::preprocessRequires () {
|
|
size_t start = 0, end = 0;
|
|
// comment out requires
|
|
while((start = this->m_preprocessed.find("#require", end)) != std::string::npos) {
|
|
// TODO: CHECK FOR ERRORS HERE
|
|
size_t lineEnd = this->m_preprocessed.find_first_of('\n', start);
|
|
sLog.out("Shader has a require block ", this->m_preprocessed.substr (start, lineEnd - start));
|
|
// replace the first two letters with a comment so the filelength doesn't change
|
|
this->m_preprocessed = this->m_preprocessed.replace(start, 2, "//");
|
|
|
|
// go to the end of the line
|
|
end = lineEnd;
|
|
}
|
|
}
|
|
|
|
void CShaderUnit::parseComboConfiguration (const std::string& content, int defaultValue) {
|
|
// TODO: SUPPORT REQUIRES SO WE PROPERLY FOLLOW THE REQUIRED CHAIN
|
|
json data = json::parse (content);
|
|
const auto combo = jsonFindRequired (data, "combo", "cannot parse combo information");
|
|
// ignore type as it seems to be used only on the editor
|
|
// 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_usedCombos.emplace (*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 no combo is defined just load the default settings
|
|
if (defvalue == data.end ()) {
|
|
// TODO: PROPERLY SUPPORT EMPTY COMBOS
|
|
this->m_discoveredCombos.emplace (*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_discoveredCombos.emplace (*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 CShaderUnit::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
|
|
// TODO: MAYBE EVEN CONNECT THESE TO THE CORRESPONDING PROPERTY SO THINGS ARE UPDATED AS THE ORIGIN VALUES CHANGE?
|
|
// TODO: MAKE USE OF PARSERS INSTEAD OF CORE
|
|
if (type == "vec4") {
|
|
parameter = new Variables::CShaderVariableVector4 (WallpaperEngine::Core::aToVector4 (*defvalue));
|
|
} else if (type == "vec3") {
|
|
parameter = new Variables::CShaderVariableVector3 (WallpaperEngine::Core::aToVector3 (*defvalue));
|
|
} else if (type == "vec2") {
|
|
parameter = new Variables::CShaderVariableVector2 (WallpaperEngine::Core::aToVector2 (*defvalue));
|
|
} else if (type == "float") {
|
|
if (defvalue->is_string ()) {
|
|
parameter = new Variables::CShaderVariableFloat (strtof32 ((defvalue->get<std::string> ()).c_str (), nullptr));
|
|
} else {
|
|
parameter = new Variables::CShaderVariableFloat (*defvalue);
|
|
}
|
|
} else if (type == "int") {
|
|
if (defvalue->is_string ()) {
|
|
parameter = new Variables::CShaderVariableInteger (strtol((defvalue->get<std::string> ()).c_str (), nullptr, 10));
|
|
} else {
|
|
parameter = new Variables::CShaderVariableInteger (*defvalue);
|
|
}
|
|
} 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 ());
|
|
const auto requireany = data.find ("requireany");
|
|
const auto require = data.find ("require");
|
|
// now convert it to integer
|
|
// TODO: BETTER CONVERSION HERE
|
|
size_t index = value - '0';
|
|
|
|
if (combo != data.end ()) {
|
|
// TODO: CLEANUP HOW THIS IS DETERMINED FIRST
|
|
// if the texture exists (and is not null), add to the combo
|
|
auto textureSlotUsed = this->m_passTextures.find (index) != this->m_passTextures.end () || this->m_overrideTextures.find (index) != this->m_overrideTextures.end ();
|
|
bool isRequired = false;
|
|
int comboValue = 1;
|
|
|
|
if (textureSlotUsed) {
|
|
// nothing extra to do, the texture exists, the combo must be set
|
|
// these tend to not have default value
|
|
isRequired = true;
|
|
} else if (require != data.end ()) {
|
|
// this is required based on certain conditions
|
|
if (requireany != data.end () && requireany->get <bool> ()) {
|
|
// any of the values set are valid, check for them
|
|
for (const auto& item : require->items ()) {
|
|
const std::string& macro = item.key ();
|
|
const auto it = this->m_combos.find (macro);
|
|
|
|
// if any of the values matched, this option is required
|
|
if (it == this->m_combos.end () || it->second != item.value ()) {
|
|
isRequired = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
isRequired = true;
|
|
|
|
// all values must match for it to be required
|
|
for (const auto& item : require->items ()) {
|
|
const std::string& macro = item.key ();
|
|
const auto it = this->m_combos.find (macro);
|
|
|
|
// these can not exist and that'd be fine, we just care about the values
|
|
if (it != this->m_combos.end () && it->second == item.value ()) {
|
|
isRequired = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isRequired && !textureSlotUsed) {
|
|
if (defvalue == data.end ()) {
|
|
isRequired = false;
|
|
} else {
|
|
// is the combo registered already?
|
|
// if not, add it with the default value
|
|
const auto combo_it = this->m_combos.find (*combo);
|
|
|
|
// there's already a combo providing this value, so it doesn't need to be added
|
|
if (combo_it != this->m_combos.end ()) {
|
|
isRequired = false;
|
|
// otherwise a default value must be used
|
|
} else if (defvalue->is_string ()) {
|
|
comboValue = strtol (defvalue->get <std::string> ().c_str (), nullptr, 10);
|
|
} else if (defvalue->is_number()) {
|
|
comboValue = *defvalue;
|
|
} else {
|
|
sLog.exception ("Cannot determine default value for combo ", combo->get <std::string> (), " because it's not specified by the shader and is not given a default value: ", this->m_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isRequired) {
|
|
// add the new combo to the list
|
|
this->m_discoveredCombos.emplace (*combo, comboValue);
|
|
// textures linked to combos need to be tracked too
|
|
this->m_usedCombos.emplace (*combo, true);
|
|
}
|
|
}
|
|
|
|
if (textureName != data.end ())
|
|
this->m_defaultTextures.emplace (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 != nullptr) {
|
|
parameter->setIdentifierName (*material);
|
|
parameter->setName (name);
|
|
|
|
this->m_parameters.push_back (parameter);
|
|
}
|
|
}
|
|
|
|
const ComboMap& CShaderUnit::getCombos () const {
|
|
return this->m_combos;
|
|
}
|
|
|
|
const ComboMap& CShaderUnit::getDiscoveredCombos () const {
|
|
return this->m_discoveredCombos;
|
|
}
|
|
|
|
void CShaderUnit::linkToUnit (const CShaderUnit* unit) {
|
|
this->m_link = unit;
|
|
}
|
|
|
|
const CShaderUnit* CShaderUnit::getLinkedUnit () const {
|
|
return this->m_link;
|
|
}
|
|
|
|
const std::string& CShaderUnit::compile () {
|
|
if (!this->m_final.empty ()) {
|
|
return this->m_final;
|
|
}
|
|
|
|
this->m_final = SHADER_HEADER(this->m_file);
|
|
|
|
if (this->m_type == CGLSLContext::UnitType_Fragment) {
|
|
this->m_final += FRAGMENT_SHADER_DEFINES;
|
|
} else {
|
|
this->m_final += VERTEX_SHADER_DEFINES;
|
|
}
|
|
|
|
std::map<std::string, bool> addedCombos;
|
|
|
|
// now add all the combos to the source
|
|
for (const auto& combo : this->m_combos) {
|
|
if (addedCombos.find (combo.first) == addedCombos.end ()) {
|
|
this->m_final += DEFINE_COMBO (combo.first, combo.second);
|
|
}
|
|
}
|
|
for (const auto& combo : this->m_discoveredCombos) {
|
|
if (addedCombos.find (combo.first) == addedCombos.end ()) {
|
|
this->m_final += DEFINE_COMBO (combo.first, combo.second);
|
|
}
|
|
}
|
|
if (this->m_link != nullptr) {
|
|
for (const auto& combo : this->m_link->getCombos ()) {
|
|
if (addedCombos.find (combo.first) == addedCombos.end ()) {
|
|
this->m_final += DEFINE_COMBO (combo.first, combo.second);
|
|
}
|
|
}
|
|
for (const auto& combo : this->m_link->getDiscoveredCombos ()) {
|
|
if (addedCombos.find (combo.first) == addedCombos.end ()) {
|
|
this->m_final += DEFINE_COMBO (combo.first, combo.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
// this should be the rest of the shader
|
|
this->m_final += this->m_preprocessed;
|
|
|
|
// shader compilation is handled by the pass itself, the unit doesn't have enough information for this step
|
|
return this->m_final;
|
|
}
|
|
|
|
const std::vector<Variables::CShaderVariable*>& CShaderUnit::getParameters () const {
|
|
return this->m_parameters;
|
|
}
|
|
const TextureMap& CShaderUnit::getTextures () const {
|
|
return this->m_defaultTextures;
|
|
} |