...
 
Commits (2)
/*******************************************************************************/
/* File compiler.hpp
/* Author Johannes Braun
/* Created 01.04.2018
/*
/* Wrapper for the proprietary binary format interface in OpenGL to
/* cache binary versions of loaded shaders for shorter loading times.
/*******************************************************************************/
#pragma once
#include "glsp.hpp"
namespace glshader::process
{
/* Pack a 4-byte char sequence into a uint32_t. Used in binary file section markers and format tags. */
constexpr uint32_t make_tag(const char name[4])
{
return (name[0] << 0) | (name[1] << 8) | (name[2] << 16) | (name[3] << 24);
}
/* The base format of the binary source. */
enum class format : uint32_t
{
gl_binary = make_tag("GBIN"),
spirv = make_tag("SPRV")
gl_binary = make_tag("GBIN"), /* Use system's proprietary vendor binary format. */
spirv = make_tag("SPRV") /* Use SPIR-V format. NOT SUPPORTED AT THE MOMENT! */
};
/* The resulting binary shader data. */
struct shader_binary
{
uint32_t format;
std::vector<uint8_t> data;
uint32_t format; /* The vendor binary format, used as binaryFormat parameter in glProgramBinary. */
std::vector<uint8_t> data; /* The binary data. */
};
/* A wrapper class containing state information about compiling shaders.
Derives from glsp::state and can therefore preprocess and compile shader files.
Additionally to the state class, you can set file extensions for the cached binary files,
which will be saved into the cache directory with their filename being their text-format-shader's source path's hash value.
There is also the option to set a prefix and a postfix for OpenGL shaders. This might be useful if you wish for
all shaders to have the same #version and #extension declarations, as well as layout(bindless_<object>) uniform; declarations. */
class compiler : public glsp::state
{
public:
/* A compiler constructed with this constructor will in it's unchanged state save binaries in the following path:
<cache_dir>/<shader_path_hash>.<extension>
If passed a file extension not starting with a '.', it will be prepended.*/
compiler(const std::string& extension, const glsp::files::path& cache_dir);
/* Replace the file extension with which binaries will be saved. */
void set_extension(const std::string& ext);
/* Set the directory in which compiled binaries will be saved and from where they will be loaded. */
void set_cache_dir(const glsp::files::path& dir);
/* Set a common source code prefix for all compiled shaders. This will NOT be preprocessed! */
void set_default_prefix(const std::string& prefix);
/* Set a common source code postfix for all compiled shaders. This will NOT be preprocessed! */
void set_default_postfix(const std::string& postfix);
/* Preprocess, compile, save and return binary data of the given shader file. If force_reload is set to false, the binary file already exists
and the internal time stamp matches the shader's last editing time, the binary file will be loaded and returned directly instead.
The parameters "includes" and "definitions" can add special include paths and definitions for this one compilation process. */
shader_binary compile(const glsp::files::path& shader, format format, bool force_reload = false, std::vector<glsp::files::path> includes ={}, std::vector<glsp::definition> definitions ={});
private:
......
#pragma once
/*******************************************************************************/
/* File config.hpp
/* Author Johannes Braun
/* Created 31.03.2018
/*
/* User space for own settings.
/*******************************************************************************/
#pragma once
// CUSTOM LOGGING:
// If you want to use your own logger for error logs, you can define ERR_OUTPUT(x) with x being the logged string.
// Example:
// #define ERR_OUTPUT(x) tlog_e("GLShader") << (x)
// Example for a custom stream-style logger:
// #define ERR_OUTPUT(x) my_logger("GLShader") << (x)
// NAMESPACE:
namespace glshader::process {}
......
/*******************************************************************************/
/* File definition.hpp
/* Author Johannes Braun
/* Created 30.03.2018
/*
/* Wrapper for in-code #define directives.
/*******************************************************************************/
#pragma once
#include "config.hpp"
......@@ -34,8 +42,8 @@ namespace glshader::process
/* Full initialization with name and info. */
definition(const std::string& name, const definition_info& info);
/* Parse string and construct definition from it. */
/* Format: MACRO(p0, p1, ..., pn) replacement */
/* Parse string and construct definition from it.
Format: MACRO(p0, p1, ..., pn) replacement */
static definition from_format(const std::string& str);
/* Macro name written in code. */
......
/*******************************************************************************/
/* File glsp.hpp
/* Author Johannes Braun
/* Created 01.04.2018
/*
/* All library files included once.
/*******************************************************************************/
#pragma once
#include "config.hpp"
......
/*******************************************************************************/
/* File huffman.hpp
/* Author Johannes Braun
/* Created 31.03.2018
/*
/* Provides helper functionality for compressing and uncompressing data using
/* huffman trees.
/*******************************************************************************/
#pragma once
#include "config.hpp"
......@@ -8,18 +17,24 @@
namespace glshader::process::compress::huffman
{
/* Tests a type on whether it is a container type by checking for a value_type type, as well as a resize(size_t) function and
overloads for std::size(...) and std::data(...). */
template<typename Container, typename BaseContainer = std::decay_t<std::remove_const_t<Container>>>
using enable_if_container = std::void_t<
typename BaseContainer::value_type,
decltype(std::declval<BaseContainer>().resize(size_t(0))),
decltype(std::size(std::declval<BaseContainer>())),
decltype(std::data(std::declval<BaseContainer>()))
>;
/* Contains a byte stream used when de-/encoding.
For simple std::basic_string<uint8_t> conversion, call stream.stringstream.str(), otherwise you can convert it
to any other STL contiguous-storage container using to_container<Container>(). */
struct stream {
size_t stream_length;
std::basic_stringstream<uint8_t> stringstream;
template<typename Container, typename BaseContainer = std::decay_t<std::remove_const_t<Container>>,
typename = std::void_t<
typename BaseContainer::value_type,
decltype(std::declval<BaseContainer>().resize(size_t(0))),
decltype(std::size(std::declval<BaseContainer>())),
decltype(std::data(std::declval<BaseContainer>()))
>
>
template<typename Container, typename = enable_if_container<Container>>
BaseContainer to_container()
{
BaseContainer container;
......@@ -29,11 +44,25 @@ namespace glshader::process::compress::huffman
}
};
stream encode(const std::basic_string<uint8_t>& in);
stream encode(const std::vector<uint8_t>& in);
/*******************************/
/* STL container wrapper
/*******************************/
/* Helper function calling encode(const uint8_t*, size_t) */
template<typename Container, typename = enable_if_container<Container>>
stream encode(const Container& in) { return encode(std::data(in), std::size(in)); }
/* Helper function calling decode(const uint8_t*, size_t) */
template<typename Container, typename = enable_if_container<Container>>
stream decode(const Container& in) { return decode(std::data(in), std::size(in)); }
/*******************************/
/* Base functions
/*******************************/
/* Encode a given uncompressed input with a given length into a compressed stream form using the huffman algorithm. */
stream encode(const uint8_t* in, size_t in_length);
stream decode(const std::basic_string<uint8_t>& in);
stream decode(const std::vector<uint8_t>& in);
/* Encode a given compressed input with a given length into an uncompressed stream form using the huffman algorithm. */
stream decode(const uint8_t* in, size_t in_length);
}
\ No newline at end of file
/*******************************************************************************/
/* File preprocess.hpp
/* Author Johannes Braun
/* Created 30.03.2018
/*******************************************************************************/
#pragma once
#include "definition.hpp"
......@@ -16,44 +22,50 @@ namespace glshader::process
{
namespace files = std::experimental::filesystem;
enum class shader_profile { core = 0, compat };
enum class ext_state { enable = 0, require };
/* Refers to in-shader version declaration profile, e.g. #version 450 core/compatibility */
enum class shader_profile
{
core = 0,
compatibility
};
/* Refers to in-shader extension declarations, e.g. #extension GL_ARB_some_extension : enable */
enum class ext_behavior {
enable = 0,
require,
warn,
disable
};
/* The file data after processing. */
struct processed_file
{
/* GLSL Shader version in integral form (e.g. 450) or 0 if no version tag is found. */
uint32_t version{ 0 };
/* GLSL Shader profile (core or compatibility) or core if none specified. */
shader_profile profile{ shader_profile::core };
/* The original path of the loaded file. */
files::path file_path;
/* All files included while loading the shader. */
std::set<files::path> dependencies;
/* All explicitly enabled/required glsl extensions. */
std::map<std::string, ext_state> extensions;
/* All definitions which have been defined in the shader without being undefined afterwards. */
std::map<std::string, definition_info> definitions;
/* The fully processed shader code string. */
std::string contents;
uint32_t version{ 0 }; /* GLSL Shader version in integral form (e.g. 450) or 0 if no version tag is found. */
shader_profile profile{ shader_profile::core }; /* GLSL Shader profile (core or compatibility) or core if none specified. */
files::path file_path; /* The original path of the loaded file. */
std::set<files::path> dependencies; /* All files included while loading the shader. */
std::map<std::string, ext_behavior> extensions; /* All explicitly enabled/required glsl extensions. */
std::map<std::string, definition_info> definitions; /* All definitions which have been defined in the shader without being undefined afterwards. */
std::string contents; /* The fully processed shader code string. */
};
/* Loads and processes a shader file. */
/* If being called while having a valid OpenGL context, all available extension names will be loaded and checked against when compiling. */
/* Otherwise, extension related #if statements will always be evaluated as false. */
/* file_path -- The source file to load. */
/* include_directories -- A list of include directories to search in when parsing includes. */
/* definitions -- A list of predefined definitions. */
/* Loads and processes a shader file.
If being called while having a valid OpenGL context, all available extension names will be loaded and checked against when compiling.
Otherwise, extension related #if statements will always be evaluated as false.
file_path -- The source file to load.
include_directories -- A list of include directories to search in when parsing includes.
definitions -- A list of predefined definitions. */
processed_file preprocess_file(const files::path& file_path, const std::vector<files::path>& include_directories, const std::vector<definition>& definitions);
/* Customizable function which is called when a syntax error was detected. */
/* You can redefine ERR_OUTPUT(str) in config.h. */
/* Customizable function which is called when a syntax error was detected.
You can redefine ERR_OUTPUT(str) in config.h. */
inline void syntax_error(const files::path& file, const int line, const std::string& reason)
{
ERR_OUTPUT("Error in " + file.string() + ":" + std::to_string(line) + ": " + reason);
}
/* A preprocessor state holding include directories and definitions. */
/* Can be used as a global default for when processing shaders, or as a slightly more flexible way to add definitions and include directories. */
/* A preprocessor state holding include directories and definitions.
Can be used as a global default for when processing shaders, or as a slightly more flexible way to add definitions and include directories. */
class state
{
public:
......@@ -67,8 +79,8 @@ namespace glshader::process
/* Remove a persistent include directory. */
void remove_include_dir(const files::path& dir);
/* Stacks all persistent include directories and definitions onto the ones passed as parameters (Therefore the need to copy), */
/* and calls the global glsp::preprocess_file function. */
/* Stacks all persistent include directories and definitions onto the ones passed as parameters (Therefore the need to copy),
and calls the global glsp::preprocess_file function. */
processed_file preprocess_file(const files::path& file_path, std::vector<files::path> include_directories ={}, std::vector<definition> definitions ={});
protected:
......
......@@ -3,6 +3,7 @@
#include <glsp/huffman.hpp>
#include "../preprocessor/files.hpp"
#include "../opengl/loader.hpp"
#include "../strings.hpp"
#include <cassert>
#include <fstream>
......@@ -78,7 +79,7 @@ namespace glshader::process
if (!(glCreateShaderProgramv && glGetProgramiv && glGetProgramInfoLog && glDeleteProgram && glGetProgramBinary))
{
result.success = false;
syntax_error("Function Loader", 0, "Unable to load required OpenGL functions. Please check whether the current context is valid and supports GL_ARB_separate_shader_objects.");
syntax_error("Function Loader", 0, strings::serr_loader_failed);
return result;
}
......@@ -112,8 +113,9 @@ namespace glshader::process
}
compiler::compiler(const std::string& extension, const glsp::files::path& cache_dir)
: _extension(extension), _cache_dir(cache_dir)
: _cache_dir(cache_dir)
{
set_extension(extension);
}
void compiler::set_extension(const std::string& ext)
......@@ -234,7 +236,7 @@ namespace glshader::process
result.format = internal_format;
} break;
case format::spirv:
syntax_error("Loader", 0, "SPIR-V is currently not supported.");
syntax_error("Loader", 0, strfmt(strings::serr_unsupported, "SPIR-V"));
default:
result.data.clear();
return result;
......
#include "eval.hpp"
#include "control.hpp"
#include "../strings.hpp"
#include <cstring>
#include <list>
......@@ -96,7 +97,7 @@ namespace glshader::process::impl::operation
++c;
if (c - begin > len)
{
syntax_error(file, line, "Unexpected end of brackets.");
syntax_error(file, line, strings::serr_eval_end_of_brackets);
return 0;
}
if (*c == '(') ++stk;
......
......@@ -3,6 +3,8 @@
#include "classify.hpp"
#include "control.hpp"
#include "skip.hpp"
#include "extensions.hpp"
#include "../strings.hpp"
#include <sstream>
......@@ -14,6 +16,8 @@ namespace glshader::process::impl::macro
bool is_defined(const std::string& val, const processed_file& processed)
{
if (std::strncmp(val.data(), "GL_", 3) && ext::extension_available(val))
return true;
return processed.definitions.count(val) != 0;
}
......@@ -56,7 +60,7 @@ namespace glshader::process::impl::macro
if (inputs.size() != info.parameters.size() || (info.parameters.size() >= inputs.size() - 1 && inputs.back() ==
"..."))
{
syntax_error(current_file, current_line, "Macro " + name + ": non-matching argument count.");
syntax_error(current_file, current_line, strfmt(strings::serr_non_matching_argc, name));
return "";
}
......
......@@ -2,6 +2,7 @@
#include <glsp/config.hpp>
/* Impl */
#include "../strings.hpp"
#include "eval.hpp"
#include "control.hpp"
#include "classify.hpp"
......@@ -101,11 +102,11 @@ namespace glshader::process
else if (cls::is_token_equal(text_ptr, "compatibility", 13))
{
processed.definitions["GL_compatibility_profile"] = 1;
processed.profile = shader_profile::compat;
processed.profile = shader_profile::compatibility;
}
else
{
syntax_error(current_file, current_line, "Unrecognized profile: " + std::string(text_ptr, skip::to_endline(text_ptr)) + ". Using core.");
syntax_error(current_file, current_line, strfmt(strings::serr_unrecognized_profile, std::string(text_ptr, skip::to_endline(text_ptr))));
processed.definitions["GL_core_profile"] = 1;
processed.profile = shader_profile::core;
}
......@@ -127,27 +128,28 @@ namespace glshader::process
text_ptr = skip::space((*(text_ptr - 1) == ':') ? text_ptr : skip::space(name_end) + 1);
if (cls::is_token_equal(text_ptr, "require", 7))
if (extension == "all")
{
processed.extensions[extension] = ext_state::require;
processed.definitions[extension] = 1;
}
else if (cls::is_token_equal(text_ptr, "enable", 6))
{
processed.extensions[extension] = ext_state::enable;
if(ext::extension_available(extension))
processed.definitions[extension] = 1;
if (cls::is_token_equal(text_ptr, "warn", 6))
processed.extensions[extension] = ext_behavior::warn;
else if (cls::is_token_equal(text_ptr, "disable", 6))
processed.extensions[extension] = ext_behavior::disable;
else
syntax_error(current_file, current_line, strfmt(strings::serr_extension_all_behavior, std::string(text_ptr, skip::to_endline(text_ptr))));
}
else
{
syntax_error(current_file, current_line,
"Unrecognized extension requirement: " +
std::string(text_ptr, skip::to_endline(text_ptr)) +
". Has to be \"require\" or \"enable\". Using \"enable\"...");
processed.extensions[extension] = ext_state::enable;
processed.definitions[extension] = 1;
if (cls::is_token_equal(text_ptr, "require", 7))
processed.extensions[extension] = ext_behavior::require;
else if (cls::is_token_equal(text_ptr, "enable", 6))
processed.extensions[extension] = ext_behavior::enable;
else if (cls::is_token_equal(text_ptr, "warn", 6))
processed.extensions[extension] = ext_behavior::warn;
else if (cls::is_token_equal(text_ptr, "disable", 6))
processed.extensions[extension] = ext_behavior::disable;
else
syntax_error(current_file, current_line, strfmt(strings::serr_extension_behavior, std::string(text_ptr, skip::to_endline(text_ptr))));
}
while (!cls::is_newline(text_ptr))
result << *text_ptr++;
}
......@@ -365,7 +367,7 @@ namespace glshader::process
}
else if (cls::is_eof(text_ptr))
{
syntax_error(current_file, current_line, "No closing endif or else found for if-expression in line " + std::to_string(ifline) + ".");
syntax_error(current_file, current_line, strfmt(strings::serr_no_endif_else, ifline));
return;
}
}
......@@ -399,7 +401,7 @@ namespace glshader::process
}
else if (cls::is_eof(text_ptr))
{
syntax_error(current_file, current_line, "no closing endif found.");
syntax_error(current_file, current_line, strings::serr_no_endif);
return;
}
......@@ -438,8 +440,7 @@ namespace glshader::process
}
else
{
syntax_error(current_file, current_line,
"Invalid line directive, did not find closing \".");
syntax_error(current_file, current_line, strings::serr_invalid_line);
current_file = files::path(std::string(text_ptr + 1, file_name_end));
processed.definitions["__FILE__"] = current_file.string();
......@@ -464,7 +465,7 @@ namespace glshader::process
if ((include_filename.front() != '\"' && include_filename.back() != '\"') && (include_filename.
front() != '<' && include_filename.back() != '>'))
{
syntax_error(current_file, current_line, "Include must be in \"...\" or <...>");
syntax_error(current_file, current_line, strings::serr_invalid_include);
return;
}
files::path file = { std::string(include_filename.begin() + 1, include_filename.end() - 1) };
......@@ -486,7 +487,7 @@ namespace glshader::process
if (!files::exists(file))
{
syntax_error(current_file, current_line, "File not found: " + std::string(include_filename.begin() + 1, include_filename.end() - 1));
syntax_error(current_file, current_line, strfmt(strings::serr_file_not_found, std::string(include_filename.begin() + 1, include_filename.end() - 1)));
return;
}
......
#pragma once
namespace glshader::process
{
template<typename... Args>
std::string strfmt(const std::string& format, Args ... args)
{
size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1;
std::string buf;
buf.resize(size);
std::snprintf(buf.data(), size, format.c_str(), args...);
return buf;
}
namespace strings
{
constexpr const char* serr_unrecognized_profile = "Unrecognized #version profile: %s. Using core.";
constexpr const char* serr_extension_all_behavior = "Cannot use #extension behavior \"%s\", must be \"warn\" or \"disable\".";
constexpr const char* serr_extension_behavior = "Unrecognized #extension behavior \"%s\", must be \"require\", \"enable\", \"warn\" or \"disable\".";
constexpr const char* serr_no_endif_else = "No closing #endif or #else found for if-expression in line %i.";
constexpr const char* serr_no_endif = "No closing #endif found.";
constexpr const char* serr_invalid_line = "Invalid line directive, did not find closing \".";
constexpr const char* serr_invalid_include = "Include must be in \"...\" or <...>.";
constexpr const char* serr_file_not_found = "File not found: %s";
constexpr const char* serr_eval_end_of_brackets = "Unexpected end of brackets.";
constexpr const char* serr_non_matching_argc = "Macro %s: non-matching argument count.";
constexpr const char* serr_loader_failed = "Unable to load required OpenGL functions. Please check whether the current context is valid and supports GL_ARB_separate_shader_objects.";
constexpr const char* serr_unsupported = "%s is currently not supported.";
}
}
\ No newline at end of file