cgv
Loading...
Searching...
No Matches
shader_code.cxx
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>
11
12#ifdef WIN32
13#pragma warning(disable:4996)
14#endif
15
16using namespace cgv::base;
17using namespace cgv::type;
18using namespace cgv::utils;
19
20namespace cgv {
21 namespace render {
22
23std::map<std::string, std::string> shader_code::code_cache;
24
25std::map<std::string, std::string> shader_code::shader_file_name_map;
26
28
34
36{
37 return "shader_config";
38}
39
42{
43 return
44 rh.reflect_member("shader_path", shader_path) &&
45 rh.reflect_member("show_file_paths", show_file_paths);
46}
47
50{
51 static shader_config_ptr config;
52 if (!config) {
53 config = shader_config_ptr(new shader_config);
54 if (getenv("CGV_SHADER_PATH"))
55 config->shader_path = getenv("CGV_SHADER_PATH");
56 else if (getenv("CGV_DIR"))
57 config->shader_path =
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";
67 }
68 return config;
69}
70
71void shader_code::decode_if_base64(std::string& content) {
72//#ifdef _WIN32
73 if (!content.empty()) {
74 // test if the first character is equal to the base64 prefix (ANSI 'paragraph' char with hexcode A7)
75 if(content[0] == char(0xA7))
76 content = decode_base64(content.substr(1));
77 }
78/*#else
79 if (content.size() > 1) {
80 if ((uint8_t&)content[0]==0xC2 && (uint8_t&)content[1]==0xA7) // UTF-8 for '�'
81 content = decode_base64(content.substr(2));
82 else if (
83 content.size() > 2 &&
84 (int)content[0] == -17 &&
85 (int)content[1] == -65 &&
86 (int)content[2] == -67) {
87 content = decode_base64(content.substr(3));
88 }
89 }
90#endif*/
91}
92
95std::string shader_code::get_last_error(const std::string& file_name, const std::string& last_error)
96{
97 std::string fn = shader_code::find_file(file_name);
98 std::vector<line> lines;
100 std::string formated_error;
101 for (unsigned int i = 0; i<lines.size(); ++i) {
102 std::string l = to_string((const token&)(lines[i]));
103 if (to_upper(l.substr(0, 5)) == "ERROR") {
104 std::vector<token> toks;
105 tokenizer(l).set_sep(":").set_ws("").bite_all(toks);
106 formated_error += fn + "(";
107 if (toks.size() > 4) {
108 formated_error += to_string(toks[4])+") : error G0001: ";
109 if (toks.size() > 6)
110 formated_error += l.substr(toks[6].begin-&l[0]);
111 }
112 else {
113 formated_error += "1) : error G0000: ";
114 if (toks.size() > 2)
115 formated_error += l.substr(toks[2].begin-&l[0]);
116 }
117 formated_error += "\n";
118 }
119 else {
120 std::vector<token> toks;
121 tokenizer(l).set_sep(":()").set_ws("").bite_all(toks);
122 unsigned int i, j, k; int n;
123 for (i=0; i<toks.size(); ++i) {
124 if (to_string(toks[i]) == "(")
125 break;
126 }
127 for (j=0; j<toks.size(); ++j) {
128 if (to_string(toks[j]) == ")")
129 break;
130 }
131 for (k=0; k<toks.size(); ++k) {
132 if (to_string(toks[k]) == ":")
133 break;
134 }
135 if (k < toks.size() && i < j && j < k && is_integer(toks[i].end, toks[j].begin, n)) {
136 std::string _fn = fn;
137 int shader_id;
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;
141 if (si < get_shader_config()->inserted_shader_file_names.size())
142 _fn = get_shader_config()->inserted_shader_file_names[si];
143 }
144 else {
145 if ((unsigned)shader_id < get_shader_config()->shader_file_names.size())
146 _fn = get_shader_config()->shader_file_names[shader_id];
147 }
148 }
149 formated_error += _fn + l.substr(toks[i].begin - &l[0])+"\n";
150 }
151 else
152 formated_error += l+"\n";
153 }
154 }
155 return formated_error;
156}
157
160{
161 st = ST_DETECT;
162}
163
165{
166 if (ctx_ptr && ctx_ptr->make_current())
168 else
169 if (handle)
170 std::cerr << "shader code not destructed correctly" << std::endl;
171}
172
175{
176 if (!handle)
177 return;
178 ctx.shader_code_destruct(*this);
179 handle = 0;
180}
181
182std::string shader_code::find_file(const std::string& file_name, bool search_exhaustive)
183{
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));
186 if (it != ref_resource_file_map().end())
187 return file_name;
188 else
189 return "";
190 }
191 if (file::exists(file_name))
192 return file_name;
193
194 std::string try_name = file::get_path(ref_prog_name()) + "/" + file_name;
195 if (file::exists(try_name))
196 return try_name;
197
198 std::map<std::string, resource_file_info>::const_iterator it =
199 ref_resource_file_map().find(file_name);
200 if (it != ref_resource_file_map().end()) {
201 if (it->second.file_offset == -1)
202 return std::string("str://") + file_name;
203 else
204 return std::string("res://") + file_name;
205 }
206 if (get_shader_config()->shader_path.empty()) {
207 try_name = std::string("glsl/") + file_name;
208 if (file::exists(try_name))
209 return try_name;
210 try_name = file::get_path(ref_prog_name()) + "/glsl/" + file_name;
211 if (file::exists(try_name))
212 return try_name;
213 return "";
214 }
215
217 std::string path_list = get_shader_config()->shader_path;
218
219 size_t pos = 0;
220 do {
221 size_t end_pos = path_list.find_first_of(';', pos);
222 std::string path;
223 if(end_pos == std::string::npos) {
224 path = path_list.substr(pos);
225 pos = path_list.length();
226 } else {
227 path = path_list.substr(pos, end_pos - pos);
228 pos = end_pos + 1;
229 }
230
231 std::vector<std::string> file_names;
232 dir::glob(path, file_names, "*gl*", true);
233
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')
239 shader_file_name_map.emplace(file::get_file_name(file_name), file_name);
240 }
241 }
242 } while(pos < path_list.length());
243
245 }
246
247 std::map<std::string, std::string>::const_iterator file_name_map_it = shader_file_name_map.find(file_name);
248 if(file_name_map_it != shader_file_name_map.end()) {
249 try_name = file_name_map_it->second;
250 if(file::exists(try_name))
251 return try_name;
252 } else if(!search_exhaustive) {
253 return "";
254 }
255
256 return file::find_in_paths(file_name, get_shader_config()->shader_path, true);
257}
258
259std::string shader_code::retrieve_code(const std::string& file_name, bool use_cache, std::string* _last_error) {
260
261 std::string source = "";
262
263 if(use_cache) {
264 auto it = code_cache.find(file_name);
265 if(it != code_cache.end()) {
266 source = it->second;
267 }
268 } else {
269 code_cache.clear();
270 }
271
272 if(source.empty())
273 source = read_code_file(file_name, _last_error);
274
275 if(use_cache)
276 code_cache.emplace(file_name, source);
277
278 return source;
279}
280
281ShaderType shader_code::detect_shader_type(const std::string& file_name)
282{
283 std::string ext = to_lower(file::get_extension(file_name));
284 ShaderType st = ST_VERTEX;
285 if (ext == "glfs" || ext == "pglfs")
286 st = ST_FRAGMENT;
287 else if (ext == "glgs" || ext == "pglgs")
288 st = ST_GEOMETRY;
289 else if (ext == "glcs" || ext == "pglcs")
290 st = ST_COMPUTE;
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")
296 st = ST_TASK;
297 else if(ext == "glms" || ext == "pglms")
298 st = ST_MESH;
299 return st;
300}
301
302std::string shader_code::resolve_includes(const std::string& source, bool use_cache, std::set<std::string>& included_file_names, std::string* _last_error)
303{
304 const std::string identifier = "#include ";
305
306 std::string resolved_source = "";
307
308 std::vector<line> lines;
309 split_to_lines(source, lines);
310
311 for(size_t i = 0; i < lines.size(); ++i) {
312 std::string current_line = to_string(lines[i]);
313
314 // search for the include identifier
315 size_t identifier_pos = current_line.find(identifier);
316 if(identifier_pos != std::string::npos) {
317 // remove identifier and all content before; an include directive must be the first and only statement on a line
318 current_line.erase(0, identifier_pos + identifier.length());
319
320 // trim whitespace
321 trim(current_line);
322
323 if(current_line.length() > 0) {
324 // remove quotation marks, leaving only the include path
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);
327
328 // check whether this file was already included and skip if this is the case
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);
332 } else {
333 current_line = "";
334 }
335 }
336
337 // skip this line if nothing needs to be included (removes the include statement from the code)
338 if(current_line == "")
339 continue;
340 }
341
342 resolved_source += current_line + '\n';
343 }
344
345 return resolved_source;
346}
347
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);
351}
352
353void shader_code::resolve_version_and_extensions(std::string& source) {
354 struct index_range {
355 size_t begin = 0;
356 size_t end = 0;
357
358 bool empty() const {
359 return begin >= end;
360 }
361
362 size_t length() const {
363 return end - begin;
364 }
365 };
366
367 struct preprocessor_directive_t {
368 enum Type {
369 kUndefined,
370 kVersion,
371 kExtension
372 };
373
374 Type type = kUndefined;
375 index_range type_range;
376 index_range content_range;
377 // TODO: Replace with semantics for replace comment //! and conditional expansion //?
378 bool is_special_comment = false;
379 };
380
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 },
384 };
385
386 enum class CommentType {
387 kNone,
388 kLine,
389 kBlock,
390 kSpecial
391 };
392
393 std::vector<preprocessor_directive_t> directives;
394
395 // read source string and collect all known directives in source
396 size_t i = 0;
397
398 CommentType comment = CommentType::kNone;
399 while(i < source.length()) {
400 char c = source[i];
401
402 switch(c) {
403 case '#':
404 if(comment == CommentType::kNone || comment == CommentType::kSpecial) {
405 // search for the next non-escaped newline which is not preceded by a backslash
406 size_t end = i;
407 do {
408 end = source.find('\n', end + 1);
409 } while(end != std::string::npos && source[end - 1] == '\\');
410
411 if(end == std::string::npos)
412 end = source.length();
413
414 // There must be at least one space that separates the directive type and its value/content between indices i and end.
415 // A valid directive also must have a value/content.
416
417 // TODO: support space between # and directive type
418
419 size_t space = source.find(' ', i + 1);
420 if(space < end) {
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);
431 }
432 }
433 comment = CommentType::kNone;
434 i = end;
435 }
436 break;
437 case '/':
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;
443 ++i;
444 }
445 break;
446 case '*':
447 if(comment == CommentType::kBlock && (i + 1) < source.length()) {
448 if(source[i + 1] == '/')
449 comment = CommentType::kNone;
450 ++i;
451 }
452 break;
453 case '!':
454 case '?':
455 if(comment == CommentType::kLine)
456 comment = CommentType::kSpecial;
457 break;
458 case '\n':
459 if(comment != CommentType::kBlock)
460 comment = CommentType::kNone;
461 break;
462 default:
463 break;
464 }
465
466 ++i;
467 }
468
469 struct version_t {
470 size_t number = 0;
471 bool has_profile = false;
472 bool is_core_profile = false;
473 };
474
475 // find the highest version number and profile
476 version_t highest_version;
477 for(const preprocessor_directive_t& directive : directives) {
478 if(directive.type != preprocessor_directive_t::Type::kVersion)
479 continue;
480
481 std::string content = cgv::utils::trim(source.substr(directive.content_range.begin, directive.content_range.length()));
482
483 std::vector<cgv::utils::token> tokens;
484 cgv::utils::split_to_tokens(content, tokens, "", true, "", "", " \t");
485
486 version_t version;
487
488 if(!tokens.empty()) {
489 if(!cgv::utils::from_string(version.number, to_string(tokens[0])))
490 version.number = 0;
491 }
492
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;
501 }
502 }
503
504 if(version.number > highest_version.number)
505 highest_version.number = version.number;
506
507 if(version.has_profile) {
508 if(highest_version.has_profile) {
509 if(version.is_core_profile)
510 highest_version.is_core_profile = true;
511 } else {
512 highest_version.has_profile = true;
513 highest_version.is_core_profile = version.is_core_profile;
514 }
515 }
516 }
517
518 // disable all non-commented version and extension directives by changing them to comments
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)) {
521 // Prevent expensive insertion operatons by simply overwriting the first two characters of the directive, since it is no longer needed anyway.
522 // This also has the advantage that the indices stay valid.
523 source[directive.type_range.begin] = '/';
524 source[directive.type_range.begin + 1] = '/';
525 }
526 }
527
528 // build a new header for the shader by first adding the version directive using the highest version
529 std::string header = "#version " + std::to_string(highest_version.number);
530 if(highest_version.has_profile) {
531 header += " ";
532 header += highest_version.is_core_profile ? "core" : "compatibility";
533 }
534 header += "\n";
535
536 // next add all extensions directly after the version to comply with the specification of some newer shader versions
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";
540 }
541 header += '\n';
542
543 source.insert(0, header);
544}
545
548{
549 return st;
550}
551
553std::string shader_code::read_code_file(const std::string &file_name, std::string* last_error)
554{
555 std::string source;
556 std::string fn = find_file(file_name);
557 if (fn.empty()) {
558 if (last_error) {
559 *last_error = "could not find shader file ";
560 *last_error += file_name;
561 }
562 return "";
563 }
564 if (!cgv::base::read_data_file(fn, source, true)) {
565 if (last_error) {
566 *last_error = "could not read shader file ";
567 *last_error += fn;
568 }
569 return "";
570 }
571 if (get_shader_config()->show_file_paths)
572 std::cout << "read shader code <" << fn << ">" << std::endl;
573
574 decode_if_base64(source);
575
576 if (file::get_extension(file_name)[0] == 'p') {
577 std::string code;
578 get_shader_config()->inserted_shader_file_names.clear();
579 std::string paths = file::get_path(fn);
580 if (!get_shader_config()->shader_path.empty())
581 paths = paths+";"+get_shader_config()->shader_path;
582
583 cgv::ppp::ph_processor php(paths, true);
584 php.configure_insert_to_shader(&get_shader_config()->inserted_shader_file_names);
585 if (!php.parse_string(source))
586 return "";
587 if (!php.process_to_string(code))
588 return "";
589 cgv::ppp::clear_variables();
590 source = code;
591 }
592 return source;
593}
594
596bool shader_code::read_code(const context& ctx, const std::string &file_name, ShaderType st, const shader_compile_options& options)
597{
598 if (st == ST_DETECT)
599 st = detect_shader_type(file_name);
600
601 // get source code from cache or read file
602 std::string source = retrieve_code(file_name, ctx.is_shader_file_cache_enabled(), &last_error);
603
604 source = resolve_includes(source, ctx.is_shader_file_cache_enabled());
605
606 resolve_version_and_extensions(source);
607
608 set_defines_and_snippets(source, options);
609
610 if (st == ST_VERTEX && ctx.get_gpu_vendor_id() == GPUVendorID::GPU_VENDOR_AMD)
611 set_vertex_attrib_locations(source);
612
613 if (source.empty())
614 return false;
615
616 return set_code(ctx, source, st);
617}
618
620bool shader_code::set_code(const context& ctx, const std::string &source, ShaderType _st)
621{
622 st = _st;
623 destruct(ctx);
624 ctx_ptr = &ctx;
625 return ctx.shader_code_create(*this, st, source);
626}
627
629void shader_code::set_defines_and_snippets(std::string& source, const shader_compile_options& options)
630{
631 enum class DirectiveType {
632 kUndefined,
633 kDefine,
634 kSnippet
635 };
636
637 struct directive_t {
638 DirectiveType type = DirectiveType::kUndefined;
639 std::string identifier {};
640 std::string replacement_list {};
641 };
642
643 if(options.empty())
644 return;
645
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();
649
650 std::vector<cgv::utils::line> lines;
651 split_to_lines(source, lines);
652 for(size_t i = 0; i < lines.size(); ++i) {
653 const cgv::utils::line& line = lines[i];
654
655 if(line.empty())
656 continue;
657
658 DirectiveType directive_type = DirectiveType::kUndefined;
659
660 // Preprocessor directives and snippet markers must appear on separate lines. Only whitespace characters are allowed to appear before them.
661 const char* ptr = line.begin;
662 for(; ptr < line.end; ++ptr) {
663 char c = *ptr;
664
665 if(c == '#') {
666 directive_type = DirectiveType::kDefine;
667 break;
668 } else if(c == '/') {
669 if(line.end - ptr > 7) {
670 if(cgv::utils::token(ptr, ptr + 8) == "//$cgv::") {
671 directive_type = DirectiveType::kSnippet;
672 break;
673 }
674 }
675 }
676
677 if(!is_space(c))
678 break;
679 }
680
681 if(directive_type != DirectiveType::kUndefined) {
682 std::vector<cgv::utils::token> tokens;
683 split_to_tokens(cgv::utils::token(ptr, line.end), tokens, "");
684
685 directive_t* directive = nullptr;
686
687 switch(directive_type) {
688 case DirectiveType::kDefine:
689 // A valid preprocessor directive will have at least two tokens (directive type and identifier)
690 if(tokens.size() > 1) {
691 std::string first_token_str = to_string(tokens.front());
692
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]);
698
699 // Any following tokens are considered to be the value (replacement_list) of the define.
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);
703 }
704 }
705 }
706 break;
707 case DirectiveType::kSnippet:
708 // A valid snippet directive will have one token (identifier).
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()), "" };
713 }
714 break;
715 default:
716 break;
717 }
718
719 if(directive) {
720 directives[directive->identifier] = directive;
721 directives_per_line[i] = directive;
722 }
723 }
724 }
725
726 std::vector<std::pair<std::string, std::string>> additional_defines;
727
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;
732 } else {
733 additional_defines.push_back({ define.first, define.second });
734 }
735 }
736
737 const shader_compile_options::string_map& snippets = options.get_snippets();
738 if(!snippets.empty())
739 additional_defines.push_back({ "CGV_USE_SNIPPETS", "" });
740
741 std::string out;
742 // The output string is going to have a similar length as the input.
743 out.reserve(source.length());
744
745 for(size_t i = 0; i < lines.size(); ++i) {
746 const cgv::utils::line& line = lines[i];
747
748 auto it = directives_per_line.find(i);
749 if(it != directives_per_line.end()) {
750 const directive_t* directive = it->second;
751
752 switch(directive->type) {
753 case DirectiveType::kDefine:
754 out += "#define " + directive->identifier + " " + directive->replacement_list;
755 break;
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;
760 });
761 if(it != snippets.end())
762 out += it->second;
763 else
764 out += to_string(line);
765 }
766 break;
767 default:
768 break;
769 }
770 } else {
771 out += to_string(line);
772 }
773
774 out += '\n';
775
776 // Put additional defines after version directive.
777 if(version_line_index == i) {
778 out += '\n';
779 for(const auto& define : additional_defines)
780 out += "#define " + define.first + " " + define.second + '\n';
781 }
782 }
783
784 for(const auto& pair : directives)
785 delete pair.second;
786
787 directives.clear();
788 directives_per_line.clear();
789
790 source = std::move(out);
791}
792
794void shader_code::set_vertex_attrib_locations(std::string& source)
795{
796 struct vertex_attribute {
797 token tok;
798 int location = -1;
799 std::string type = "";
800 std::string name = "";
801
802 vertex_attribute(const token& tok) : tok(tok) {}
803
804 std::string to_string() {
805 std::string str = "layout (location = ";
806 str += std::to_string(location);
807 str += ") in ";
808 str += type + " ";
809 str += name + ";";
810 return str;
811 }
812 };
813
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;
822
823 source = strip_cpp_comments(source);
824
825 split_to_tokens(source, parts, "", true, "", "", ";\n");
826
827 attrib_flags.resize(parts.size(), false);
828
829 size_t part_idx = 0;
830
831 // First read the version. The only thing allowed before the version statement is comments and empty lines.
832 // Both get removed bevore splitting the source into parts, so the first part must be the version.
833 split_to_tokens(parts[part_idx], tokens, "", true, "", "", " \t");
834
835 if(tokens.size() > 1 && tokens[0] == "#version") {
836 version_idx = part_idx;
837
838 std::string number_str = to_string(tokens[1]);
839 char* p_end;
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);
843
844 if(tokens.size() == 3 && tokens[2] == "core")
845 is_core = true;
846 }
847
848 ++part_idx;
849
850 // Search for the optional NO_UPGRADE define, which must come directly after the version statement.
851 tokens.clear();
852 split_to_tokens(parts[part_idx], tokens, "", true, "", "", " \t");
853
854 if(tokens.size() > 1 && tokens[0] == "#define") {
855 if(tokens[1] == "NO_UPGRADE") {
856 no_upgrade = true;
857 ++part_idx;
858 }
859 }
860
861 // return if the shader should not be upgraded
862 if(no_upgrade)
863 return;
864
865 // now get all vertex attributes
866 for(; part_idx < parts.size(); ++part_idx) {
867 auto& tok = parts[part_idx];
868
869 while(tok.begin < tok.end && is_space(*tok.begin))
870 ++tok.begin;
871
872 if(tok.size() > 2) {
873 int parentheses_count = 0;
874 bool is_new_word = true;
875 bool was_new_word = true;
876
877 for(unsigned i = 0; i < tok.size() - 2; ++i) {
878 char c = tok[i];
879
880 if(c == '(')
881 ++parentheses_count;
882
883 if(c == ')')
884 --parentheses_count;
885
886 is_new_word = is_space(c) || c == ')';
887
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;
892 break;
893 }
894 }
895
896 was_new_word = is_new_word;
897 }
898 }
899 }
900
901 // return if no vertex attributes were found
902 if(attribs.size() == 0)
903 return;
904
905 int max_location = -1;
906 for(size_t i = 0; i < attribs.size(); ++i) {
907 auto& attrib = attribs[i];
908
909 tokens.clear();
910 split_to_tokens(attrib.tok, tokens, "", true, "", "", " \t()");
911
912 size_t size = tokens.size();
913
914 if(tokens.size() > 2) {
915 // last two entries must be type and name
916 attrib.type = to_string(tokens[size - 2]);
917 attrib.name = to_string(tokens[size - 1]);
918
919 // find location if present
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] == '=')
924 equals_idx = j;
925 }
926
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]);
930 char* p_end;
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);
935 }
936 }
937 }
938 }
939 }
940
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;
945 }
946
947 size_t attrib_idx = 0;
948 size_t accumulate_offset = 0;
949 size_t content_offset = reinterpret_cast<size_t>(source.data());
950
951 for(size_t i = 0; i < parts.size(); ++i) {
952 auto& tok = parts[i];
953
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);
957
958 size_t offset = token_begin - content_offset;
959 size_t length = token_end - token_begin;
960
961 std::string str = "";
962
963 if(attrib_flags[i]) {
964 auto& attrib = attribs[attrib_idx];
965 ++attrib_idx;
966 str = attrib.to_string() + "\n";
967 } else {
968 version_number = std::max(version_number, 330);
969 str = "#version " + std::to_string(version_number) + (is_core ? " core" : "");
970 }
971
972 offset += accumulate_offset;
973 accumulate_offset += str.length() - length - 1;
974
975 std::string first_part = source.substr(0, offset);
976 std::string second_part = source.substr(offset + length + 1);
977
978 source = first_part + str + second_part;
979 }
980 }
981}
982
985{
986 if (!ctx.shader_code_compile(*this)) {
987 user_data = 0;
988 return false;
989 }
990 int id = 1;
991 user_data = (void*&) id;
992 return true;
993}
994
996bool shader_code::read_and_compile(const context& ctx, const std::string &file_name, ShaderType st, const shader_compile_options& options, bool show_error)
997{
998 if (!read_code(ctx, file_name, st, options))
999 return false;
1000
1001 if (!compile(ctx)) {
1002 if (show_error)
1003 std::cerr << get_last_error(file_name, last_error).c_str() << std::endl;
1004 return false;
1005 }
1006
1007 return true;
1008}
1009
1012{
1013 return user_data != 0;
1014}
1015
1016
1017 }
1018}
1019
1020struct shader_config_registration
1021{
1022 shader_config_registration()
1023 {
1024 register_object(cgv::render::get_shader_config(), "register global shader config");
1025 }
1026};
1027
1028shader_config_registration shader_config_registration_instance;
More advanced text processing for splitting text into lines or tokens.
bool empty() const
check if pointer is not yet set
Definition ref_ptr.h:230
void clear()
set to null pointer
Definition ref_ptr.h:238
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.
Definition context.h:670
virtual GPUVendorID get_gpu_vendor_id() const
device information
Definition context.cxx:318
bool is_shader_file_cache_enabled() const
whether the shader file caches are enabled
Definition context.cxx:569
const context * ctx_ptr
keep pointer to my context
Definition context.h:363
std::string last_error
a string that contains the last error
Definition context.h:365
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.
Definition shader_code.h:73
const string_map & get_macros() const
Return const reference to defined macros.
Definition shader_code.h:89
bool empty() const
Return true if no options are set.
Definition shader_code.h:78
const string_map & get_snippets() const
Return const reference to defined snippets.
Definition shader_code.h:94
the tokenizer allows to split text into tokens in a convenient way.
Definition tokenizer.h:68
tokenizer & set_sep(const std::string &sep, bool merge)
set the list of separators and specify whether succeeding separators are merged into single tokens
Definition tokenizer.cxx:59
tokenizer & set_ws(const std::string &ws)
set the list of white spaces, that separate tokens and are skipped
Definition tokenizer.cxx:38
the base namespace holds the base hierarchy, support for plugin registration and signals
Definition action.cxx:4
bool read_data_file(const std::string &file_name, std::string &content, bool ascii)
read ascii file into a string
Definition import.cxx:388
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
Definition register.cxx:143
void register_object(base_ptr object, const std::string &options)
register an object and send event to all current registration ref_listeners()
Definition register.cxx:581
std::string & ref_prog_name()
return a refence to the name of the started executable
Definition register.cxx:125
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
Definition context.cxx:1099
cgv::data::ref_ptr< shader_config > shader_config_ptr
type of ref counted pointer to shader configuration
Definition shader_code.h:46
ShaderType
different shader types
Definition context.h:543
namespace for compile time type information
Definition bool32_t.h:7
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...
Definition scan.cxx:449
std::string & trim(std::string &str, const std::string &chars)
trim white space or other characters from start and end of string
Definition scan.cxx:790
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
Definition scan.cxx:39
char to_upper(char c)
convert char to upper case
Definition scan.cxx:106
bool is_space(char c)
check if char is a whitespace
Definition scan.cxx:12
std::string decode_base64(std::string const &encoded_string)
decode a base64 encoded string
Definition convert.cxx:105
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
Definition print.h:11
a globally unique shader config is registered by default when the cgv library is used.
Definition shader_code.h:26
std::string get_type_name() const
return "shader_config"
bool show_file_paths
whether to output full paths of read shaders
Definition shader_code.h:32
shader_config()
construct config without file name tracing
bool trace_file_names
whether to keep track of file names
Definition shader_code.h:30
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
Definition shader_code.h:28
representation of a token in a text by two pointers begin and end, that point to the first character ...
Definition token.h:18
bool empty() const
return whether the token is empty
Definition token.h:56
const char * begin
pointers that define the range of characters
Definition token.h:20