diff --git a/CMakeLists.txt b/CMakeLists.txt index bc40e61..d4aa351 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,6 @@ find_package(SDL2 REQUIRED COMPONENTS SDL2::SDL2-static SDL2::SDL2main CONFIG) find_package(OpenAL REQUIRED CONFIG) find_library(physfs REQUIRED NAMES physfs-static PATHS "${CMAKE_PREFIX_PATH}/lib") - # Determine dependencies set(STATIC_LIBS dr_wav diff --git a/src/gl/shader-template.cpp b/src/gl/shader-template.cpp new file mode 100644 index 0000000..b503a8a --- /dev/null +++ b/src/gl/shader-template.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "gl/shader-template.hpp" +#include +#include + +namespace gl { + +shader_template::shader_template(const std::string& source_code) +{ + source(source_code); +} + +shader_template::shader_template() +{} + +void shader_template::source(const std::string& source) +{ + // Reset template + template_source.clear(); + vertex_directives.clear(); + fragment_directives.clear(); + geometry_directives.clear(); + define_directives.clear(); + + // Iterate through source line-by-line + std::istringstream source_stream(source); + std::string line; + while (std::getline(source_stream, line)) + { + std::string token; + std::istringstream line_stream(line); + + // Detect `#pragma` directives + if (line_stream >> token && token == "#pragma") + { + if (line_stream >> token) + { + // Map line numbers of supported directives + if (token == "define") + { + if (line_stream >> token) + define_directives.insert({token, template_source.size()}); + } + else if (token == "vertex") + vertex_directives.insert(template_source.size()); + else if (token == "fragment") + fragment_directives.insert(template_source.size()); + else if (token == "geometry") + geometry_directives.insert(template_source.size()); + } + } + + // Append line to template source + template_source.push_back(line); + } +} + +std::string shader_template::configure(gl::shader_stage stage, const dictionary_type& definitions) const +{ + replace_stage_directives(stage); + replace_define_directives(definitions); + + // Join vector of source lines into single string + std::ostringstream stream; + std::copy(template_source.begin(), template_source.end(), std::ostream_iterator(stream, "\n")); + return stream.str(); +} + +gl::shader_object* shader_template::compile(gl::shader_stage stage, const dictionary_type& definitions) const +{ + // Generate shader object source + std::string object_source = configure(stage, definitions); + + // Create new shader object + gl::shader_object* object = new gl::shader_object(stage); + + // Set shader object source + object->source(object_source.c_str(), object_source.length()); + + // Compile shader object + object->compile(); + + return object; +} + +gl::shader_program* shader_template::build(const dictionary_type& definitions) const +{ + gl::shader_object* vertex_object = nullptr; + gl::shader_object* fragment_object = nullptr; + gl::shader_object* geometry_object = nullptr; + + // Create shader program + gl::shader_program* program = new gl::shader_program(); + + if (has_vertex_directive()) + { + // Compile vertex shader object and attach to shader program + vertex_object = compile(gl::shader_stage::vertex, definitions); + program->attach(vertex_object); + } + + if (has_fragment_directive()) + { + // Compile fragment shader object and attach to shader program + fragment_object = compile(gl::shader_stage::fragment, definitions); + program->attach(fragment_object); + } + + if (has_geometry_directive()) + { + // Compile fragment shader object and attach to shader program + geometry_object = compile(gl::shader_stage::geometry, definitions); + program->attach(geometry_object); + } + + // Link attached shader objects into shader program + program->link(); + + if (vertex_object) + { + // Detach and delete vertex shader object + program->detach(vertex_object); + delete vertex_object; + } + + if (fragment_object) + { + // Detach and delete fragment shader object + program->detach(fragment_object); + delete fragment_object; + } + + if (geometry_object) + { + // Detach and delete geometry shader object + program->detach(geometry_object); + delete geometry_object; + } + + return program; +} + +void shader_template::replace_stage_directives(gl::shader_stage stage) const +{ + // Determine stage directives according to the shader stage being generated + const std::string vertex_directive = (stage == gl::shader_stage::vertex) ? "#define __VERTEX__" : "/* #undef __VERTEX__ */"; + const std::string fragment_directive = (stage == gl::shader_stage::fragment) ? "#define __FRAGMENT__" : "/* #undef __FRAGMENT__ */"; + const std::string geometry_directive = (stage == gl::shader_stage::geometry) ? "#define __GEOMETRY__" : "/* #undef __GEOMETRY__ */"; + + // Handle `#pragma ` directives + for (std::size_t i: vertex_directives) + template_source[i] = vertex_directive; + for (std::size_t i: fragment_directives) + template_source[i] = fragment_directive; + for (std::size_t i: geometry_directives) + template_source[i] = geometry_directive; +} + +void shader_template::replace_define_directives(const dictionary_type& definitions) const +{ + // For each `#pragma define ` directive + for (const auto& define_directive: define_directives) + { + // Get a reference to the directive line + std::string& line = template_source[define_directive.second]; + + // Check if the corresponding definition was given by the configuration + auto definitions_it = definitions.find(define_directive.first); + if (definitions_it != definitions.end()) + { + // Definition found, Replace `#pragma define ` with `#define ` or `#define ` + line = "#define " + define_directive.first; + if (!definitions_it->second.empty()) + line += " " + definitions_it->second; + } + else + { + // Definition not found, replace `#pragma define ` with the comment `/* #undef */`. + line = "/* #undef " + define_directive.first + " */"; + } + } +} + +bool shader_template::has_vertex_directive() const +{ + return !vertex_directives.empty(); +} + +bool shader_template::has_fragment_directive() const +{ + return !fragment_directives.empty(); +} + +bool shader_template::has_geometry_directive() const +{ + return !geometry_directives.empty(); +} + +bool shader_template::has_define_directive(const std::string& name) const +{ + return (define_directives.find(name) != define_directives.end()); +} + +} // namespace gl diff --git a/src/gl/shader-template.hpp b/src/gl/shader-template.hpp new file mode 100644 index 0000000..8df19ee --- /dev/null +++ b/src/gl/shader-template.hpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_SHADER_TEMPLATE_HPP +#define ANTKEEPER_SHADER_TEMPLATE_HPP + +#include "gl/shader-object.hpp" +#include "gl/shader-program.hpp" +#include +#include +#include +#include + +namespace gl { + +/** + * Shader templates can be used to generate multiple shader variants from a single source. + * + * Shader templates support the following preprocessor directives: + * + * * `#pragma vertex`: Replaced with `#define __VERTEX__` when generating vertex shader objects. + * * `#pragma fragment`: Replaced with `#define __FRAGMENT__` when generating fragment shader objects. + * * `#pragma geometry`: Replaced with `#define __GEOMETRY__` when generating geometry shader objects. + * * `#pragma define `: Will be replaced with `#define ` if its definition is passed to the shader template. + * + * @see gl::shader_stage + * @see gl::shader_object + * @see gl::shader_program + */ +class shader_template +{ +public: + /// Container of definitions used to replace `#pragma define ` directives. + typedef std::unordered_map dictionary_type; + + /** + * Creates a shader template and sets its source code. + * + * @param source_code String containing the shader template source code. + * + * @see shader_template::source(const std::string&) + */ + shader_template(const std::string& source_code); + + /** + * Creates a shader template. + */ + shader_template(); + + /** + * Replaces the source code of the shader template. + * + * @param source_code String containing the shader template source code. + */ + void source(const std::string& source_code); + + /** + * Configures shader object source code given a shader stage and template dictionary. + * + * @param stage Shader stage of the shader object to generate. Instances of `#pragma ` in the template source will be replaced with `#define ____`. + * @param definitions Container of definitions used to replace `#pragma define ` directives. + * @return Configured shader object source code. + */ + std::string configure(gl::shader_stage stage, const dictionary_type& definitions) const; + + /** + * Configures and compiles a shader object. + * + * @param stage Shader stage of the shader object to generate. Instances of `#pragma ` in the template source will be replaced with `#define ____`. + * @param definitions Container of definitions used to replace `#pragma define ` directives. + * @return Compiled shader object. + * + * @exception std::runtime_error Any exceptions thrown by gl::shader_object. + */ + gl::shader_object* compile(gl::shader_stage stage, const dictionary_type& definitions) const; + + /** + * Configures and compiles shader objects, then links them into a shader program. Shader object stages are determined according to the presence of `#pragma ` directives. + * + * @param definitions Container of definitions used to replace `#pragma define ` directives. + * @return Linked shader program. + * + * @exception std::runtime_error Any exceptions thrown by gl::shader_object or gl::shader_program. + * + * @see has_vertex_directive() const + * @see has_fragment_directive() const + * @see has_geometry_directive() const + */ + gl::shader_program* build(const dictionary_type& definitions) const; + + /// Returns `true` if the template source contains one or more `#pragma vertex` directive. + bool has_vertex_directive() const; + + /// Returns `true` if the template source contains one or more `#pragma fragment` directive. + bool has_fragment_directive() const; + + /// Returns `true` if the template source contains one or more `#pragma geometry` directive. + bool has_geometry_directive() const; + + /** + * Returns `true` if the template source contains one or more instance of `#pragma define `. + * + * @param name Definition name. + */ + bool has_define_directive(const std::string& name) const; + +private: + void replace_stage_directives(gl::shader_stage stage) const; + void replace_define_directives(const dictionary_type& definitions) const; + + mutable std::vector template_source; + std::unordered_set vertex_directives; + std::unordered_set fragment_directives; + std::unordered_set geometry_directives; + std::multimap define_directives; +}; + +} // namespace gl + +#endif // ANTKEEPER_SHADER_TEMPLATE_HPP diff --git a/src/resources/shader-program-loader.cpp b/src/resources/shader-program-loader.cpp index 461dd6e..88a1f22 100644 --- a/src/resources/shader-program-loader.cpp +++ b/src/resources/shader-program-loader.cpp @@ -20,26 +20,8 @@ #include "resources/resource-loader.hpp" #include "resources/resource-manager.hpp" #include "resources/text-file.hpp" -#include "gl/shader-stage.hpp" -#include "gl/shader-object.hpp" -#include "gl/shader-program.hpp" +#include "gl/shader-template.hpp" #include -#include -#include - -/** - * Tokenizes a line of shader source code. - */ -static std::vector tokenize(const std::string& line) -{ - std::vector tokens; - std::string token; - std::istringstream linestream(line); - while (linestream >> token) - tokens.push_back(token); - - return tokens; -} /** * Handles `#pragma include` directives by loading the specified text files and inserting them in place. @@ -49,179 +31,75 @@ static void handle_includes(text_file* source, resource_manager* resource_manage // For each line in the source for (std::size_t i = 0; i < source->size(); ++i) { - // Tokenize line - std::vector tokens = tokenize((*source)[i]); - - // Look for `#pragma include` directives - if (tokens.size() == 3 && tokens[0] == "#pragma" && tokens[1] == "include") - { - // Get path to include file - std::string path = tokens[2].substr(1, tokens[2].length() - 2); - - // Load include file - if (!resource_manager->load(path)) - { - std::string message = std::string("Failed to load shader include file \"") + path + std::string("\""); - throw std::runtime_error(message.c_str()); - } - text_file include_file = *(resource_manager->load(path)); - - // Handle `#pragma include` directives inside include file - handle_includes(&include_file, resource_manager); - - // Replace #pragma include directive with include file contents - source->erase(source->begin() + i); - source->insert(source->begin() + i, include_file.begin(), include_file.end()); - i += include_file.size() - 1; - } - } -} - -/** - * Injects the `DEBUG` or `NDEBUG` macros after the `#version` directive. - */ -static void inject_debug_macro(text_file* source) -{ - // For each line in the source - for (std::size_t i = 0; i < source->size(); ++i) - { - // Tokenize line - std::vector tokens = tokenize((*source)[i]); - - // Inject DEBUG and NDEBUG macros - if (!tokens.empty() && tokens[0] == "#version") - { - #if defined(NDEBUG) - source->insert(source->begin() + i + 1, "#define NDEBUG"); - #else - source->insert(source->begin() + i + 1, "#define DEBUG"); - #endif - - return; - } - } -} - -/** - * Injects a shader type macro definition after the `#version` directive. - */ -static void inject_shader_stage_macro(text_file* source, gl::shader_stage stage) -{ - const char* vertex_macro = "#define _VERTEX"; - const char* fragment_macro = "#define _FRAGMENT"; - const char* geometry_macro = "#define _GEOMETRY"; - - const char* macro = nullptr; - if (stage == gl::shader_stage::vertex) - macro = vertex_macro; - else if (stage == gl::shader_stage::fragment) - macro = fragment_macro; - else if (stage == gl::shader_stage::geometry) - macro = geometry_macro; - - // For each line in the source - for (std::size_t i = 0; i < source->size(); ++i) - { - // Tokenize line - std::vector tokens = tokenize((*source)[i]); - - // Inject shader stage macro - if (!tokens.empty() && tokens[0] == "#version") - { - source->insert(source->begin() + i + 1, macro); - return; - } - } -} - -static std::unordered_set detect_shader_stages(text_file* source) -{ - std::unordered_set types; - - // For each line in the source - for (std::size_t i = 0; i < source->size(); ++i) - { - // Tokenize line - std::vector tokens = tokenize((*source)[i]); - - // Look for `#pragma include` directives - if (tokens.size() == 2 && tokens[0] == "#pragma") + std::string token; + std::istringstream line_stream((*source)[i]); + + // If line contains a `#pragma include` directive + if (line_stream >> token && token == "#pragma" && + line_stream >> token && token == "include") { - if (tokens[1] == "vertex") + // If third token is enclosed in quotes or angled brackets + if (line_stream >> token && token.size() > 2 && + ((token.front() == '\"' && token.back() == '\"') || + (token.front() == '<' && token.back() == '>'))) { - types.insert(gl::shader_stage::vertex); + // Extract include path + std::string path = token.substr(1, token.length() - 2); + + // Load include file + if (!resource_manager->load(path)) + { + (*source)[i] = "#error file not found (" + path + ")"; + } + else + { + // Create copy of include file + text_file include_file = *(resource_manager->load(path)); + + // Handle `#pragma include` directives inside include file + handle_includes(&include_file, resource_manager); + + // Replace #pragma include directive with include file contents + source->erase(source->begin() + i); + source->insert(source->begin() + i, include_file.begin(), include_file.end()); + i += include_file.size() - 1; + } } - else if (tokens[1] == "fragment") + else { - types.insert(gl::shader_stage::fragment); - } - else if (tokens[1] == "geometry") - { - types.insert(gl::shader_stage::geometry); + (*source)[i] = "#error malformed include directive"; } } } - - return types; -} - -static std::string generate_source_buffer(const std::vector& source) -{ - std::ostringstream stream; - std::copy(source.begin(), source.end(), std::ostream_iterator(stream, "\n")); - return stream.str(); } template <> gl::shader_program* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) { - // Load shader source - text_file* source = resource_loader::load(resource_manager, file); - + // Load shader template source + text_file source_lines = *resource_loader::load(resource_manager, file); + // Handle `#pragma include` directives - handle_includes(source, resource_manager); - - // Inject `DEBUG` or `NDEBUG` macro definitions - inject_debug_macro(source); - - // Detect declared shader types via the `#pragma vertex`, `#pragma fragment` and `#pragma geometry` directives - std::unordered_set shade_stages = detect_shader_stages(source); + handle_includes(&source_lines, resource_manager); - // Load detected shaders - std::list objects; - for (gl::shader_stage stage: shade_stages) - { - text_file stage_source = *source; - inject_shader_stage_macro(&stage_source, stage); - std::string source_buffer = generate_source_buffer(stage_source); - - gl::shader_object* object = new gl::shader_object(stage); - object->source(source_buffer.c_str(), source_buffer.length()); - object->compile(); - - objects.push_back(object); - } - - // Create shader program - gl::shader_program* program = new gl::shader_program(); + // Join vector of source lines into single string + std::ostringstream stream; + std::copy(source_lines.begin(), source_lines.end(), std::ostream_iterator(stream, "\n")); - // Attach shader objects - for (gl::shader_object* object: objects) - program->attach(object); + // Create shader template + gl::shader_template* shader_template = new gl::shader_template(stream.str()); - // Link shader program - if (!program->link()) + // Build shader program + gl::shader_program* program = shader_template->build(gl::shader_template::dictionary_type()); + + // Check if shader program was linked successfully + if (!program->was_linked()) { throw std::runtime_error("Shader program linking failed: " + program->get_info_log()); } - - // Detach and delete shader objects - for (gl::shader_object* object: objects) - { - program->detach(object); - delete object; - } + + // Destroy shader template + delete shader_template; return program; } -