1#include <cgv/base/base.h>
2#include "shader_code.h"
3#include <cgv/base/register.h>
4#include <cgv/base/import.h>
6#include <cgv/utils/tokenizer.h>
7#include <cgv/ppp/ph_processor.h>
8#include <cgv/utils/dir.h>
9#include <cgv/utils/file.h>
10#include <cgv/type/variant.h>
13#pragma warning(disable:4996)
37 return "shader_config";
54 if (getenv(
"CGV_SHADER_PATH"))
56 else if (getenv(
"CGV_DIR"))
58 std::string(getenv(
"CGV_DIR")) +
"/libs/cgv_g2d/glsl;" +
59 std::string(getenv(
"CGV_DIR")) +
"/libs/cgv_gl/glsl;"+
60 std::string(getenv(
"CGV_DIR")) +
"/libs/cgv_gpgpu/glsl;" +
61 std::string(getenv(
"CGV_DIR")) +
"/libs/cgv_overlay/glsl;" +
62 std::string(getenv(
"CGV_DIR")) +
"/libs/holo_disp;" +
63 std::string(getenv(
"CGV_DIR")) +
"/libs/plot/glsl;"+
64 std::string(getenv(
"CGV_DIR")) +
"/libs/cgv_proc/glsl;"+
65 std::string(getenv(
"CGV_DIR")) +
"/libs/rgbd_render;"+
66 std::string(getenv(
"CGV_DIR")) +
"/plugins/examples";
73 if (!content.empty()) {
75 if(content[0] ==
char(0xA7))
98 std::vector<line> lines;
100 std::string formated_error;
101 for (
unsigned int i = 0; i<lines.size(); ++i) {
103 if (
to_upper(l.substr(0, 5)) ==
"ERROR") {
104 std::vector<token> toks;
106 formated_error += fn +
"(";
107 if (toks.size() > 4) {
108 formated_error +=
to_string(toks[4])+
") : error G0001: ";
110 formated_error += l.substr(toks[6].begin-&l[0]);
113 formated_error +=
"1) : error G0000: ";
115 formated_error += l.substr(toks[2].begin-&l[0]);
117 formated_error +=
"\n";
120 std::vector<token> toks;
122 unsigned int i, j, k;
int n;
123 for (i=0; i<toks.size(); ++i) {
127 for (j=0; j<toks.size(); ++j) {
131 for (k=0; k<toks.size(); ++k) {
135 if (k < toks.size() && i < j && j < k &&
is_integer(toks[i].end, toks[j].begin, n)) {
136 std::string _fn = fn;
138 if (i > 0 &&
is_integer(toks[i-1].begin, toks[i-1].end, shader_id)) {
139 if (shader_id >= 1000) {
140 unsigned si = shader_id-1000;
149 formated_error += _fn + l.substr(toks[i].begin - &l[0])+
"\n";
152 formated_error += l+
"\n";
155 return formated_error;
170 std::cerr <<
"shader code not destructed correctly" << std::endl;
178 ctx.shader_code_destruct(*
this);
184 if (file_name.substr(0, 6) ==
"str://" || file_name.substr(0, 6) ==
"res://") {
185 std::map<std::string, resource_file_info>::const_iterator it =
ref_resource_file_map().find(file_name.substr(6));
191 if (file::exists(file_name))
194 std::string try_name = file::get_path(
ref_prog_name()) +
"/" + file_name;
195 if (file::exists(try_name))
198 std::map<std::string, resource_file_info>::const_iterator it =
201 if (it->second.file_offset == -1)
202 return std::string(
"str://") + file_name;
204 return std::string(
"res://") + file_name;
207 try_name = std::string(
"glsl/") + file_name;
208 if (file::exists(try_name))
210 try_name = file::get_path(
ref_prog_name()) +
"/glsl/" + file_name;
211 if (file::exists(try_name))
221 size_t end_pos = path_list.find_first_of(
';', pos);
223 if(end_pos == std::string::npos) {
224 path = path_list.substr(pos);
225 pos = path_list.length();
227 path = path_list.substr(pos, end_pos - pos);
231 std::vector<std::string> file_names;
232 dir::glob(path, file_names,
"*gl*",
true);
234 for(
const auto& file_name : file_names) {
235 std::string ext = file::get_extension(file_name);
236 if(ext.length() > 2) {
237 if(ext[0] ==
'g' && ext[1] ==
'l' ||
238 ext[0] ==
'p' && ext[1] ==
'g' && ext[2] ==
'l')
242 }
while(pos < path_list.length());
247 std::map<std::string, std::string>::const_iterator file_name_map_it =
shader_file_name_map.find(file_name);
249 try_name = file_name_map_it->second;
250 if(file::exists(try_name))
252 }
else if(!search_exhaustive) {
261 std::string source =
"";
283 std::string ext =
to_lower(file::get_extension(file_name));
285 if (ext ==
"glfs" || ext ==
"pglfs")
287 else if (ext ==
"glgs" || ext ==
"pglgs")
289 else if (ext ==
"glcs" || ext ==
"pglcs")
291 else if (ext ==
"gltc" || ext ==
"pgltc")
292 st = ST_TESS_CONTROL;
293 else if (ext ==
"glte" || ext ==
"pglte")
294 st = ST_TESS_EVALUATION;
295 else if(ext ==
"glts" || ext ==
"pglts")
297 else if(ext ==
"glms" || ext ==
"pglms")
302std::string shader_code::resolve_includes(
const std::string& source,
bool use_cache, std::set<std::string>& included_file_names, std::string* _last_error)
304 const std::string identifier =
"#include ";
306 std::string resolved_source =
"";
308 std::vector<line> lines;
311 for(
size_t i = 0; i < lines.size(); ++i) {
312 std::string current_line =
to_string(lines[i]);
315 size_t identifier_pos = current_line.find(identifier);
316 if(identifier_pos != std::string::npos) {
318 current_line.erase(0, identifier_pos + identifier.length());
323 if(current_line.length() > 0) {
325 std::string include_file_name = current_line.substr(1, current_line.length() - 2);
326 std::string include_source =
retrieve_code(include_file_name, use_cache, _last_error);
329 if(included_file_names.find(include_file_name) == included_file_names.end()) {
330 included_file_names.insert(include_file_name);
331 current_line = resolve_includes(include_source, use_cache, included_file_names, _last_error);
338 if(current_line ==
"")
342 resolved_source += current_line +
'\n';
345 return resolved_source;
348std::string shader_code::resolve_includes(
const std::string& source,
bool use_cache, std::string* _last_error) {
349 std::set<std::string> included_file_names;
350 return resolve_includes(source, use_cache, included_file_names, _last_error);
353void shader_code::resolve_version_and_extensions(std::string& source) {
362 size_t length()
const {
367 struct preprocessor_directive_t {
374 Type type = kUndefined;
375 index_range type_range;
376 index_range content_range;
378 bool is_special_comment =
false;
381 static const std::map<std::string, preprocessor_directive_t::Type> identifier_to_type = {
382 {
"#version", preprocessor_directive_t::Type::kVersion },
383 {
"#extension", preprocessor_directive_t::Type::kExtension },
386 enum class CommentType {
393 std::vector<preprocessor_directive_t> directives;
398 CommentType comment = CommentType::kNone;
399 while(i < source.length()) {
404 if(comment == CommentType::kNone || comment == CommentType::kSpecial) {
408 end = source.find(
'\n', end + 1);
409 }
while(end != std::string::npos && source[end - 1] ==
'\\');
411 if(end == std::string::npos)
412 end = source.length();
419 size_t space = source.find(
' ', i + 1);
421 index_range type_range = { i, space };
422 std::string type_string = source.substr(type_range.begin, type_range.length());
423 auto it = identifier_to_type.find(type_string);
424 if(it != identifier_to_type.end()) {
425 preprocessor_directive_t directive;
426 directive.type = it->second;
427 directive.type_range = { i, space };
428 directive.content_range = { space + 1, end };
429 directive.is_special_comment = comment == CommentType::kSpecial;
430 directives.push_back(directive);
433 comment = CommentType::kNone;
438 if(comment == CommentType::kNone && (i + 1) < source.length()) {
439 if(source[i + 1] ==
'/')
440 comment = CommentType::kLine;
441 else if(source[i + 1] ==
'*')
442 comment = CommentType::kBlock;
447 if(comment == CommentType::kBlock && (i + 1) < source.length()) {
448 if(source[i + 1] ==
'/')
449 comment = CommentType::kNone;
455 if(comment == CommentType::kLine)
456 comment = CommentType::kSpecial;
459 if(comment != CommentType::kBlock)
460 comment = CommentType::kNone;
471 bool has_profile =
false;
472 bool is_core_profile =
false;
476 version_t highest_version;
477 for(
const preprocessor_directive_t& directive : directives) {
478 if(directive.type != preprocessor_directive_t::Type::kVersion)
481 std::string content =
cgv::utils::trim(source.substr(directive.content_range.begin, directive.content_range.length()));
483 std::vector<cgv::utils::token> tokens;
488 if(!tokens.empty()) {
493 if(tokens.size() > 1) {
494 std::string profile =
to_string(tokens[1]);
495 if(profile ==
"core") {
496 version.has_profile =
true;
497 version.is_core_profile =
true;
498 }
else if(profile ==
"compatibility") {
499 version.has_profile =
true;
500 version.is_core_profile =
false;
504 if(version.number > highest_version.number)
505 highest_version.number = version.number;
507 if(version.has_profile) {
508 if(highest_version.has_profile) {
509 if(version.is_core_profile)
510 highest_version.is_core_profile =
true;
512 highest_version.has_profile =
true;
513 highest_version.is_core_profile = version.is_core_profile;
519 for(
const preprocessor_directive_t& directive : directives) {
520 if(!directive.is_special_comment && (directive.type == preprocessor_directive_t::Type::kVersion || directive.type == preprocessor_directive_t::Type::kExtension)) {
523 source[directive.type_range.begin] =
'/';
524 source[directive.type_range.begin + 1] =
'/';
529 std::string header =
"#version " + std::to_string(highest_version.number);
530 if(highest_version.has_profile) {
532 header += highest_version.is_core_profile ?
"core" :
"compatibility";
537 for(
const preprocessor_directive_t& directive : directives) {
538 if(directive.type == preprocessor_directive_t::Type::kExtension)
539 header +=
"#extension " + source.substr(directive.content_range.begin, directive.content_range.length()) +
"\n";
543 source.insert(0, header);
572 std::cout <<
"read shader code <" << fn <<
">" << std::endl;
576 if (file::get_extension(file_name)[0] ==
'p') {
579 std::string paths = file::get_path(fn);
584 php.configure_insert_to_shader(&
get_shader_config()->inserted_shader_file_names);
585 if (!php.parse_string(source))
587 if (!php.process_to_string(code))
589 cgv::ppp::clear_variables();
606 resolve_version_and_extensions(source);
608 set_defines_and_snippets(source, options);
611 set_vertex_attrib_locations(source);
625 return ctx.shader_code_create(*
this,
st, source);
631 enum class DirectiveType {
638 DirectiveType
type = DirectiveType::kUndefined;
639 std::string identifier {};
640 std::string replacement_list {};
646 std::map<std::string, directive_t*> directives;
647 std::map<size_t, directive_t*> directives_per_line;
648 size_t version_line_index = std::numeric_limits<size_t>::max();
650 std::vector<cgv::utils::line> lines;
652 for(
size_t i = 0; i < lines.size(); ++i) {
653 const cgv::utils::line& line = lines[i];
658 DirectiveType directive_type = DirectiveType::kUndefined;
661 const char* ptr = line.
begin;
662 for(; ptr < line.end; ++ptr) {
666 directive_type = DirectiveType::kDefine;
668 }
else if(c ==
'/') {
669 if(line.end - ptr > 7) {
670 if(cgv::utils::token(ptr, ptr + 8) ==
"//$cgv::") {
671 directive_type = DirectiveType::kSnippet;
681 if(directive_type != DirectiveType::kUndefined) {
682 std::vector<cgv::utils::token> tokens;
685 directive_t* directive =
nullptr;
687 switch(directive_type) {
688 case DirectiveType::kDefine:
690 if(tokens.size() > 1) {
691 std::string first_token_str =
to_string(tokens.front());
693 if(first_token_str ==
"#version") {
694 version_line_index = i;
695 }
else if(first_token_str ==
"#define" && !tokens[1].empty()) {
696 directive =
new directive_t{ directive_type };
697 directive->identifier =
to_string(tokens[1]);
700 if(tokens.size() > 2) {
701 cgv::utils::token value_token(tokens[2].begin, tokens.back().end);
702 directive->replacement_list =
to_string(value_token);
707 case DirectiveType::kSnippet:
709 if(tokens.size() == 1) {
710 tokens.front().begin += 3;
711 if(!tokens.front().empty())
712 directive =
new directive_t{ directive_type,
to_string(tokens.front()),
"" };
720 directives[directive->identifier] = directive;
721 directives_per_line[i] = directive;
726 std::vector<std::pair<std::string, std::string>> additional_defines;
728 for(
const auto& define : options.
get_macros()) {
729 auto it = directives.find(define.first);
730 if(it != directives.end() && it->second->type == DirectiveType::kDefine) {
731 it->second->replacement_list = define.second;
733 additional_defines.push_back({ define.first, define.second });
737 const shader_compile_options::string_map& snippets = options.
get_snippets();
738 if(!snippets.empty())
739 additional_defines.push_back({
"CGV_USE_SNIPPETS",
"" });
743 out.reserve(source.length());
745 for(
size_t i = 0; i < lines.size(); ++i) {
746 const cgv::utils::line& line = lines[i];
748 auto it = directives_per_line.find(i);
749 if(it != directives_per_line.end()) {
750 const directive_t* directive = it->second;
752 switch(directive->type) {
753 case DirectiveType::kDefine:
754 out +=
"#define " + directive->identifier +
" " + directive->replacement_list;
756 case DirectiveType::kSnippet:
757 if(!snippets.empty()) {
758 auto it = std::find_if(snippets.begin(), snippets.end(), [directive](
const std::pair<std::string, std::string>& snippet) {
759 return directive->identifier ==
"cgv::" + snippet.first;
761 if(it != snippets.end())
777 if(version_line_index == i) {
779 for(
const auto& define : additional_defines)
780 out +=
"#define " + define.first +
" " + define.second +
'\n';
784 for(
const auto& pair : directives)
788 directives_per_line.clear();
790 source = std::move(out);
794void shader_code::set_vertex_attrib_locations(std::string& source)
796 struct vertex_attribute {
799 std::string type =
"";
800 std::string name =
"";
802 vertex_attribute(
const token& tok) : tok(tok) {}
805 std::string str =
"layout (location = ";
806 str += std::to_string(location);
814 std::vector<token> parts;
815 std::vector<token> tokens;
816 std::vector<vertex_attribute> attribs;
817 std::vector<bool> attrib_flags;
818 size_t version_idx = 0;
819 int version_number = 0;
820 bool is_core =
false;
821 bool no_upgrade =
false;
827 attrib_flags.resize(parts.size(),
false);
835 if(tokens.size() > 1 && tokens[0] ==
"#version") {
836 version_idx = part_idx;
838 std::string number_str =
to_string(tokens[1]);
840 const long num = std::strtol(number_str.c_str(), &p_end, 10);
841 if(number_str.c_str() != p_end)
842 version_number =
static_cast<int>(num);
844 if(tokens.size() == 3 && tokens[2] ==
"core")
854 if(tokens.size() > 1 && tokens[0] ==
"#define") {
855 if(tokens[1] ==
"NO_UPGRADE") {
866 for(; part_idx < parts.size(); ++part_idx) {
867 auto& tok = parts[part_idx];
869 while(tok.begin < tok.end &&
is_space(*tok.begin))
873 int parentheses_count = 0;
874 bool is_new_word =
true;
875 bool was_new_word =
true;
877 for(
unsigned i = 0; i < tok.size() - 2; ++i) {
886 is_new_word =
is_space(c) || c ==
')';
888 if(c ==
'i' && tok[i + 1] ==
'n' && tok[i + 2] ==
' ') {
889 if(was_new_word && parentheses_count == 0) {
890 attribs.push_back(vertex_attribute(tok));
891 attrib_flags[part_idx] =
true;
896 was_new_word = is_new_word;
902 if(attribs.size() == 0)
905 int max_location = -1;
906 for(
size_t i = 0; i < attribs.size(); ++i) {
907 auto& attrib = attribs[i];
912 size_t size = tokens.size();
914 if(tokens.size() > 2) {
916 attrib.type =
to_string(tokens[size - 2]);
917 attrib.name =
to_string(tokens[size - 1]);
920 size_t equals_idx = -1;
921 for(
size_t j = 0; j < size; ++j) {
922 auto& tok = tokens[j];
923 if(tok.size() == 1 && tok[0] ==
'=')
927 if(equals_idx != -1 && equals_idx > 0 && equals_idx < size - 1) {
928 if(
to_string(tokens[equals_idx - 1]) ==
"location") {
929 std::string val_str =
to_string(tokens[equals_idx + 1]);
931 const long num = std::strtol(val_str.c_str(), &p_end, 10);
932 if(val_str.c_str() != p_end) {
933 attrib.location =
static_cast<int>(num);
934 max_location = std::max(max_location, attrib.location);
941 for(
size_t i = 0; i < attribs.size(); ++i) {
942 auto& attrib = attribs[i];
943 if(attrib.location < 0)
944 attrib.location = ++max_location;
947 size_t attrib_idx = 0;
948 size_t accumulate_offset = 0;
949 size_t content_offset =
reinterpret_cast<size_t>(source.data());
951 for(
size_t i = 0; i < parts.size(); ++i) {
952 auto& tok = parts[i];
954 if(i == version_idx || attrib_flags[i]) {
955 size_t token_begin =
reinterpret_cast<size_t>(tok.begin);
956 size_t token_end =
reinterpret_cast<size_t>(tok.end);
958 size_t offset = token_begin - content_offset;
959 size_t length = token_end - token_begin;
961 std::string str =
"";
963 if(attrib_flags[i]) {
964 auto& attrib = attribs[attrib_idx];
966 str = attrib.to_string() +
"\n";
968 version_number = std::max(version_number, 330);
969 str =
"#version " + std::to_string(version_number) + (is_core ?
" core" :
"");
972 offset += accumulate_offset;
973 accumulate_offset += str.length() - length - 1;
975 std::string first_part = source.substr(0, offset);
976 std::string second_part = source.substr(offset + length + 1);
978 source = first_part + str + second_part;
986 if (!ctx.shader_code_compile(*
this)) {
991 user_data = (
void*&)
id;
1013 return user_data != 0;
1020struct shader_config_registration
1022 shader_config_registration()
More advanced text processing for splitting text into lines or tokens.
bool empty() const
check if pointer is not yet set
void clear()
set to null pointer
the pre header processor parses a pre header file and converts it to a header file or uses the inform...
the self reflection handler is passed to the virtual self_reflect() method of cgv::base::base.
bool reflect_member(const std::string &member_name, T &member_ref, bool hard_cast=false)
call this to reflect a member by member name and reference to the member.
base class for all drawables, which is independent of the used rendering API.
virtual GPUVendorID get_gpu_vendor_id() const
device information
bool is_shader_file_cache_enabled() const
whether the shader file caches are enabled
const context * ctx_ptr
keep pointer to my context
std::string last_error
a string that contains the last error
static bool shader_file_name_map_initialized
whether the shader file name map is initialized
static std::string find_file(const std::string &file_name, bool search_exhaustive=false)
Find the full path to a shader by its file name.
shader_code()
create shader a shader code object
static std::string read_code_file(const std::string &file_name, std::string *_last_error=0)
read shader code from file and return string with content or empty string if read failed
static std::map< std::string, std::string > code_cache
map that caches shader file contents indexed by their file name
ShaderType get_shader_type() const
return the shader type of this code
bool is_compiled() const
return whether shader has been compiled successfully
void destruct(const context &ctx)
destruct shader code
~shader_code()
calls the destruct method
static void decode_if_base64(std::string &content)
decode a string if it is base64 encoded
static std::string retrieve_code(const std::string &file_name, bool use_cache, std::string *_last_error)
retreive shader code either by reading the file from disk or from the cache if enabled
ShaderType st
store the shader type
static std::string get_last_error(const std::string &file_name, const std::string &last_error)
format given last error in a way that developer environments can locate errors in the source file
static std::map< std::string, std::string > shader_file_name_map
map that caches full shader file paths indexed by the shader file name
bool compile(const context &ctx)
compile attached source; returns true if successful
bool set_code(const context &ctx, const std::string &source, ShaderType st)
set shader code from string
bool read_code(const context &ctx, const std::string &file_name, ShaderType st=ST_DETECT, const shader_compile_options &options={})
read shader code from file that is searched for with find_file.
bool read_and_compile(const context &ctx, const std::string &file_name, ShaderType st=ST_DETECT, const shader_compile_options &options={}, bool show_error=true)
read shader code with read_code and compile.
static ShaderType detect_shader_type(const std::string &file_name)
detect the shader type from the extension of the given file_name, i.e.
Stores preprocessor options used for conditionally compiling shader programs.
const string_map & get_macros() const
Return const reference to defined macros.
bool empty() const
Return true if no options are set.
const string_map & get_snippets() const
Return const reference to defined snippets.
the tokenizer allows to split text into tokens in a convenient way.
tokenizer & set_sep(const std::string &sep, bool merge)
set the list of separators and specify whether succeeding separators are merged into single tokens
tokenizer & set_ws(const std::string &ws)
set the list of white spaces, that separate tokens and are skipped
the base namespace holds the base hierarchy, support for plugin registration and signals
bool read_data_file(const std::string &file_name, std::string &content, bool ascii)
read ascii file into a string
std::map< std::string, resource_file_info > & ref_resource_file_map()
return a reference to a mapping of resource file names to resource file infos
void register_object(base_ptr object, const std::string &options)
register an object and send event to all current registration ref_listeners()
std::string & ref_prog_name()
return a refence to the name of the started executable
namespace for api independent GPU programming
shader_config_ptr get_shader_config()
return a reference to the current shader configuration
std::string to_string(TextureWrap wrap)
convert texture wrap to string
cgv::data::ref_ptr< shader_config > shader_config_ptr
type of ref counted pointer to shader configuration
ShaderType
different shader types
namespace for compile time type information
namespace that holds tools that dont fit any other namespace
void split_to_tokens(const char *begin, const char *end, std::vector< token > &tokens, const std::string &separators, bool merge_separators, const std::string &open_parenthesis, const std::string &close_parenthesis, const std::string &whitespaces, unsigned int max_nr_tokens)
this function splits a text range into tokens.
std::string to_string(const std::string &v, unsigned int w, unsigned int p, bool)
specialization of conversion from string to strings
bool is_integer(const char *begin, const char *end, int &value)
check if the text range (begin,end( defines an integer value. If yes, store the value in the passed r...
std::string & trim(std::string &str, const std::string &chars)
trim white space or other characters from start and end of string
void split_to_lines(const char *global_begin, const char *global_end, std::vector< line > &lines, bool truncate_trailing_spaces)
this function splits a text range at the newline characters into single lines.
char to_lower(char c)
convert char to lower case
char to_upper(char c)
convert char to upper case
bool is_space(char c)
check if char is a whitespace
std::string decode_base64(std::string const &encoded_string)
decode a base64 encoded string
std::string strip_cpp_comments(const std::string &source, bool correct_new_lines)
remove cpp-style comments from string
bool from_string(std::string &v, const std::string &s)
specialization to extract string value from string
this header is dependency free
a globally unique shader config is registered by default when the cgv library is used.
std::string get_type_name() const
return "shader_config"
bool show_file_paths
whether to output full paths of read shaders
shader_config()
construct config without file name tracing
bool trace_file_names
whether to keep track of file names
bool self_reflect(cgv::reflect::reflection_handler &srh)
reflect the shader_path member
std::string shader_path
the path used to find shaders with the cgv::utils::file::find_in_paths function
representation of a token in a text by two pointers begin and end, that point to the first character ...
bool empty() const
return whether the token is empty
const char * begin
pointers that define the range of characters