283 lines
7.9 KiB
C++
283 lines
7.9 KiB
C++
#include "GLSLExtension.hpp"
|
|
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include "debug/Logger.hpp"
|
|
#include "io/engine_paths.hpp"
|
|
#include "typedefs.hpp"
|
|
#include "util/stringutil.hpp"
|
|
#include "coders/json.hpp"
|
|
#include "data/dv_util.hpp"
|
|
#include "coders/BasicParser.hpp"
|
|
|
|
static debug::Logger logger("glsl-extension");
|
|
|
|
using Type = PostEffect::Param::Type;
|
|
using Value = PostEffect::Param::Value;
|
|
|
|
void GLSLExtension::setPaths(const ResPaths* paths) {
|
|
this->paths = paths;
|
|
}
|
|
|
|
void GLSLExtension::loadHeader(const std::string& name) {
|
|
if (paths == nullptr) {
|
|
return;
|
|
}
|
|
io::path file = paths->find("shaders/lib/" + name + ".glsl");
|
|
std::string source = io::read_string(file);
|
|
addHeader(name, "");
|
|
|
|
auto [code, _] = process(file, source, true);
|
|
addHeader(name, std::move(code));
|
|
}
|
|
|
|
void GLSLExtension::addHeader(const std::string& name, std::string source) {
|
|
headers[name] = std::move(source);
|
|
}
|
|
|
|
void GLSLExtension::define(const std::string& name, std::string value) {
|
|
defines[name] = std::move(value);
|
|
}
|
|
|
|
const std::string& GLSLExtension::getHeader(const std::string& name) const {
|
|
auto found = headers.find(name);
|
|
if (found == headers.end()) {
|
|
throw std::runtime_error("no header '" + name + "' loaded");
|
|
}
|
|
return found->second;
|
|
}
|
|
|
|
const std::string& GLSLExtension::getDefine(const std::string& name) const {
|
|
auto found = defines.find(name);
|
|
if (found == defines.end()) {
|
|
throw std::runtime_error("name '" + name + "' is not defined");
|
|
}
|
|
return found->second;
|
|
}
|
|
|
|
const std::unordered_map<std::string, std::string>& GLSLExtension::getDefines() const {
|
|
return defines;
|
|
}
|
|
|
|
bool GLSLExtension::hasDefine(const std::string& name) const {
|
|
return defines.find(name) != defines.end();
|
|
}
|
|
|
|
bool GLSLExtension::hasHeader(const std::string& name) const {
|
|
return headers.find(name) != headers.end();
|
|
}
|
|
|
|
void GLSLExtension::undefine(const std::string& name) {
|
|
if (hasDefine(name)) {
|
|
defines.erase(name);
|
|
}
|
|
}
|
|
|
|
inline std::runtime_error parsing_error(
|
|
const io::path& file, uint linenum, const std::string& message
|
|
) {
|
|
return std::runtime_error(
|
|
"file " + file.string() + ": " + message + " at line " +
|
|
std::to_string(linenum)
|
|
);
|
|
}
|
|
|
|
inline void parsing_warning(
|
|
std::string_view file, uint linenum, const std::string& message
|
|
) {
|
|
logger.warning() << "file " + std::string(file) + ": warning: " + message +
|
|
" at line " + std::to_string(linenum);
|
|
}
|
|
|
|
inline void source_line(std::stringstream& ss, uint linenum) {
|
|
ss << "#line " << linenum << "\n";
|
|
}
|
|
|
|
static std::optional<Type> param_type_from(
|
|
const std::string& name
|
|
) {
|
|
static const std::unordered_map<std::string, Type> typeNames {
|
|
{"float", Type::FLOAT},
|
|
{"vec2", Type::VEC2},
|
|
{"vec3", Type::VEC3},
|
|
{"vec4", Type::VEC4},
|
|
};
|
|
const auto& found = typeNames.find(name);
|
|
if (found == typeNames.end()) {
|
|
return std::nullopt;
|
|
}
|
|
return found->second;
|
|
}
|
|
|
|
static Value default_value_for(Type type) {
|
|
switch (type) {
|
|
case Type::FLOAT:
|
|
return 0.0f;
|
|
case Type::VEC2:
|
|
return glm::vec2 {0.0f, 0.0f};
|
|
case Type::VEC3:
|
|
return glm::vec3 {0.0f, 0.0f, 0.0f};
|
|
case Type::VEC4:
|
|
return glm::vec4 {0.0f, 0.0f, 0.0f, 0.0f};
|
|
default:
|
|
throw std::runtime_error("unsupported type");
|
|
}
|
|
}
|
|
|
|
class GLSLParser : public BasicParser<char> {
|
|
public:
|
|
GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header)
|
|
: BasicParser(file, source), glsl(glsl) {
|
|
if (!header) {
|
|
ss << "#version " << GLSLExtension::VERSION << '\n';
|
|
}
|
|
for (auto& entry : glsl.getDefines()) {
|
|
ss << "#define " << entry.first << " " << entry.second << '\n';
|
|
}
|
|
uint linenum = 1;
|
|
source_line(ss, linenum);
|
|
|
|
clikeComment = true;
|
|
}
|
|
|
|
bool processIncludeDirective() {
|
|
skipWhitespace(false);
|
|
if (peekNoJump() != '<') {
|
|
throw error("'<' expected");
|
|
}
|
|
skip(1);
|
|
skipWhitespace(false);
|
|
auto headerName = parseName();
|
|
skipWhitespace(false);
|
|
if (peekNoJump() != '>') {
|
|
throw error("'>' expected");
|
|
}
|
|
skip(1);
|
|
skipWhitespace(false);
|
|
skipLine();
|
|
|
|
if (!glsl.hasHeader(headerName)) {
|
|
glsl.loadHeader(headerName);
|
|
}
|
|
ss << glsl.getHeader(headerName) << '\n';
|
|
source_line(ss, line);
|
|
return false;
|
|
}
|
|
|
|
bool processVersionDirective() {
|
|
parsing_warning(filename, line, "removed #version directive");
|
|
source_line(ss, line);
|
|
skipLine();
|
|
return false;
|
|
}
|
|
|
|
template<int n>
|
|
Value parseVectorValue() {
|
|
if (peekNoJump() != '[') {
|
|
throw error("'[' expected");
|
|
}
|
|
// may be more efficient but ok
|
|
auto value = json::parse(
|
|
filename,
|
|
std::string_view(source.data() + pos, source.size() - pos)
|
|
);
|
|
glm::vec<n, float> vec {};
|
|
try {
|
|
dv::get_vec<n>(value, vec);
|
|
return vec;
|
|
} catch (const std::exception& err) {
|
|
throw error(err.what());
|
|
}
|
|
}
|
|
|
|
Value parseDefaultValue(Type type, const std::string& name) {
|
|
switch (type) {
|
|
case Type::FLOAT:
|
|
return static_cast<float>(parseNumber(1).asNumber());
|
|
case Type::VEC2:
|
|
return parseVectorValue<2>();
|
|
case Type::VEC3:
|
|
return parseVectorValue<3>();
|
|
case Type::VEC4:
|
|
return parseVectorValue<4>();
|
|
default:
|
|
throw error("unsupported default value for type " + name);
|
|
}
|
|
}
|
|
|
|
bool processParamDirective() {
|
|
skipWhitespace(false);
|
|
// Parse type name
|
|
auto typeName = parseName();
|
|
auto type = param_type_from(typeName);
|
|
if (!type.has_value()) {
|
|
throw error("unsupported param type " + util::quote(typeName));
|
|
}
|
|
skipWhitespace(false);
|
|
// Parse parameter name
|
|
auto paramName = parseName();
|
|
if (params.find(paramName) != params.end()) {
|
|
throw error("duplicating param " + util::quote(paramName));
|
|
}
|
|
skipWhitespace(false);
|
|
ss << "uniform " << typeName << " " << paramName << ";\n";
|
|
|
|
auto defValue = default_value_for(type.value());
|
|
// Parse default value
|
|
if (peekNoJump() == '=') {
|
|
skip(1);
|
|
skipWhitespace(false);
|
|
defValue = parseDefaultValue(type.value(), typeName);
|
|
}
|
|
|
|
skipLine();
|
|
|
|
params[paramName] = PostEffect::Param(type.value(), std::move(defValue));
|
|
return false;
|
|
}
|
|
|
|
bool processPreprocessorDirective() {
|
|
skip(1);
|
|
|
|
auto name = parseName();
|
|
|
|
if (name == "version") {
|
|
return processVersionDirective();
|
|
} else if (name == "include") {
|
|
return processIncludeDirective();
|
|
} else if (name == "param") {
|
|
return processParamDirective();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
GLSLExtension::ProcessingResult process() {
|
|
while (hasNext()) {
|
|
skipWhitespace(false);
|
|
if (!hasNext()) {
|
|
break;
|
|
}
|
|
if (source[pos] != '#' || processPreprocessorDirective()) {
|
|
pos = linestart;
|
|
ss << readUntilEOL() << '\n';
|
|
skip(1);
|
|
}
|
|
}
|
|
return {ss.str(), std::move(params)};
|
|
}
|
|
private:
|
|
GLSLExtension& glsl;
|
|
std::unordered_map<std::string, PostEffect::Param> params;
|
|
std::stringstream ss;
|
|
};
|
|
|
|
GLSLExtension::ProcessingResult GLSLExtension::process(
|
|
const io::path& file, const std::string& source, bool header
|
|
) {
|
|
std::string filename = file.string();
|
|
GLSLParser parser(*this, filename, source, header);
|
|
return parser.process();
|
|
}
|