Skip to content

Commit c8e3e01

Browse files
committed
Add support for includes in shader files
This can be used for keeping code for common functionality in one file (e.g. lighting functions). The code which detects include statements is pretty simple so it may break if it is used in a more complicated. Circular includes are detected and treated as an error.
1 parent 0178374 commit c8e3e01

1 file changed

Lines changed: 78 additions & 4 deletions

File tree

code/graphics/opengl/gropenglshader.cpp

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,10 @@ static SCP_string opengl_shader_get_header(shader_type type_id, int flags, shade
362362
* @param flags integer variable holding a combination of SDR_* flags
363363
* @return C-string holding the complete shader source code
364364
*/
365-
static SCP_string opengl_load_shader(const char *filename)
366-
{
365+
static SCP_string opengl_load_shader(const char* filename) {
367366
SCP_string content;
368367
if (Enable_external_shaders) {
369-
CFILE *cf_shader = cfopen(filename, "rt", CFILE_NORMAL, CF_TYPE_EFFECTS);
368+
CFILE* cf_shader = cfopen(filename, "rt", CFILE_NORMAL, CF_TYPE_EFFECTS);
370369

371370
if (cf_shader != NULL) {
372371
int len = cfilelength(cf_shader);
@@ -387,11 +386,86 @@ static SCP_string opengl_load_shader(const char *filename)
387386
return content;
388387
}
389388

389+
static void handle_includes_impl(SCP_vector<SCP_string>& include_stack,
390+
SCP_stringstream& output,
391+
int& include_counter,
392+
const SCP_string& filename,
393+
const SCP_string& original) {
394+
include_stack.emplace_back(filename);
395+
auto current_source_number = include_counter + 1;
396+
397+
const char* INCLUDE_STRING = "#include";
398+
SCP_stringstream input(original);
399+
400+
int line_num = 1;
401+
for (SCP_string line; std::getline(input, line);) {
402+
auto include_start = line.find(INCLUDE_STRING);
403+
if (include_start != SCP_string::npos) {
404+
auto first_quote = line.find('"', include_start + strlen(INCLUDE_STRING));
405+
auto second_quote = line.find('"', first_quote + 1);
406+
407+
if (first_quote == SCP_string::npos || second_quote == SCP_string::npos) {
408+
Error(LOCATION,
409+
"Shader %s:%d: Malformed include line. Could not find both quote charaters.",
410+
filename.c_str(),
411+
line_num);
412+
}
413+
414+
auto file_name = line.substr(first_quote + 1, second_quote - first_quote - 1);
415+
auto existing_name = std::find_if(include_stack.begin(), include_stack.end(), [&file_name](const SCP_string& str) {
416+
return str == file_name;
417+
});
418+
if (existing_name != include_stack.end()) {
419+
SCP_stringstream stack_string;
420+
for (auto& name : include_stack) {
421+
stack_string << "\t" << name << "\n";
422+
}
423+
424+
Error(LOCATION,
425+
"Shader %s:%d: Detected cyclic include! Previous includes (top level file first):\n%s",
426+
filename.c_str(),
427+
line_num,
428+
stack_string.str().c_str());
429+
}
430+
431+
++include_counter;
432+
// The second parameter defines which source string we are currently working with. We keep track of how many
433+
// excludes have been in the file so far to specify this
434+
output << "#line 1 " << include_counter + 1 << "\n";
435+
436+
handle_includes_impl(include_stack,
437+
output,
438+
include_counter,
439+
file_name,
440+
opengl_load_shader(file_name.c_str()));
441+
442+
// We are done with the include file so now we can return to the original file
443+
output << "#line " << line_num + 1 << " " << current_source_number << "\n";
444+
} else {
445+
output << line << "\n";
446+
}
447+
448+
++line_num;
449+
}
450+
451+
include_stack.pop_back();
452+
}
453+
454+
static SCP_string handle_includes(const char* filename, const SCP_string& original) {
455+
SCP_stringstream output;
456+
SCP_vector<SCP_string> include_stack;
457+
auto include_counter = 0;
458+
459+
handle_includes_impl(include_stack, output, include_counter, filename, original);
460+
461+
return output.str();
462+
}
463+
390464
static SCP_vector<SCP_string> opengl_get_shader_content(shader_type type_id, const char* filename, int flags, shader_stage stage) {
391465
SCP_vector<SCP_string> parts;
392466
parts.push_back(opengl_shader_get_header(type_id, flags, stage));
393467

394-
parts.push_back(opengl_load_shader(filename));
468+
parts.push_back(handle_includes(filename, opengl_load_shader(filename)));
395469

396470
return parts;
397471
}

0 commit comments

Comments
 (0)