💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
5.8 KiB

  1. /*
  2. * Copyright (C) 2020 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "resources/resource-loader.hpp"
  20. #include "resources/resource-manager.hpp"
  21. #include "resources/text-file.hpp"
  22. #include "rasterizer/shader-type.hpp"
  23. #include "rasterizer/shader.hpp"
  24. #include "rasterizer/shader-program.hpp"
  25. #include <sstream>
  26. #include <iterator>
  27. /**
  28. * Tokenizes a line of shader source code.
  29. */
  30. static std::vector<std::string> tokenize(const std::string& line)
  31. {
  32. std::vector<std::string> tokens;
  33. std::string token;
  34. std::istringstream linestream(line);
  35. while (linestream >> token)
  36. tokens.push_back(token);
  37. return tokens;
  38. }
  39. /**
  40. * Handles `#pragma include` directives by loading the specified text files and inserting them in place.
  41. */
  42. static void handle_includes(text_file* source, resource_manager* resource_manager)
  43. {
  44. // For each line in the source
  45. for (std::size_t i = 0; i < source->size(); ++i)
  46. {
  47. // Tokenize line
  48. std::vector<std::string> tokens = tokenize((*source)[i]);
  49. // Look for `#pragma include` directives
  50. if (tokens.size() == 3 && tokens[0] == "#pragma" && tokens[1] == "include")
  51. {
  52. // Get path to include file
  53. std::string path = tokens[2].substr(1, tokens[2].length() - 2);
  54. // Load include file
  55. if (!resource_manager->load<text_file>(path))
  56. {
  57. std::string message = std::string("Failed to load shader include file \"") + path + std::string("\"");
  58. throw std::runtime_error(message.c_str());
  59. }
  60. text_file include_file = *(resource_manager->load<text_file>(path));
  61. // Handle `#pragma include` directives inside include file
  62. handle_includes(&include_file, resource_manager);
  63. // Replace #pragma include directive with include file contents
  64. source->erase(source->begin() + i);
  65. source->insert(source->begin() + i, include_file.begin(), include_file.end());
  66. i += include_file.size() - 1;
  67. }
  68. }
  69. }
  70. /**
  71. * Injects the `DEBUG` or `NDEBUG` macros after the `#version` directive.
  72. */
  73. static void inject_debug_macro(text_file* source)
  74. {
  75. // For each line in the source
  76. for (std::size_t i = 0; i < source->size(); ++i)
  77. {
  78. // Tokenize line
  79. std::vector<std::string> tokens = tokenize((*source)[i]);
  80. // Inject DEBUG and NDEBUG macros
  81. if (!tokens.empty() && tokens[0] == "#version")
  82. {
  83. #if defined(NDEBUG)
  84. source->insert(source->begin() + i + 1, "#define NDEBUG");
  85. #else
  86. source->insert(source->begin() + i + 1, "#define DEBUG");
  87. #endif
  88. return;
  89. }
  90. }
  91. }
  92. /**
  93. * Injects a shader type macro definition after the `#version` directive.
  94. */
  95. static void inject_shader_type_macro(text_file* source, shader_type type)
  96. {
  97. const char* vertex_macro = "#define _VERTEX";
  98. const char* fragment_macro = "#define _FRAGMENT";
  99. const char* geometry_macro = "#define _GEOMETRY";
  100. const char* macro = nullptr;
  101. if (type == shader_type::vertex)
  102. macro = vertex_macro;
  103. else if (type == shader_type::fragment)
  104. macro = fragment_macro;
  105. else if (type == shader_type::geometry)
  106. macro = geometry_macro;
  107. // For each line in the source
  108. for (std::size_t i = 0; i < source->size(); ++i)
  109. {
  110. // Tokenize line
  111. std::vector<std::string> tokens = tokenize((*source)[i]);
  112. // Inject shader type macro
  113. if (!tokens.empty() && tokens[0] == "#version")
  114. {
  115. source->insert(source->begin() + i + 1, macro);
  116. return;
  117. }
  118. }
  119. }
  120. static std::list<shader_type> detect_shader_types(text_file* source)
  121. {
  122. std::list<shader_type> types;
  123. // For each line in the source
  124. for (std::size_t i = 0; i < source->size(); ++i)
  125. {
  126. // Tokenize line
  127. std::vector<std::string> tokens = tokenize((*source)[i]);
  128. // Look for `#pragma include` directives
  129. if (tokens.size() == 2 && tokens[0] == "#pragma")
  130. {
  131. if (tokens[1] == "vertex")
  132. {
  133. types.push_back(shader_type::vertex);
  134. }
  135. else if (tokens[1] == "fragment")
  136. {
  137. types.push_back(shader_type::fragment);
  138. }
  139. else if (tokens[1] == "geometry")
  140. {
  141. types.push_back(shader_type::geometry);
  142. }
  143. }
  144. }
  145. return types;
  146. }
  147. static std::string generate_source_buffer(const std::vector<std::string>& source)
  148. {
  149. std::ostringstream stream;
  150. std::copy(source.begin(), source.end(), std::ostream_iterator<std::string>(stream, "\n"));
  151. return stream.str();
  152. }
  153. template <>
  154. shader_program* resource_loader<shader_program>::load(resource_manager* resource_manager, PHYSFS_File* file)
  155. {
  156. // Load shader source
  157. text_file* source = resource_loader<text_file>::load(resource_manager, file);
  158. // Handle `#pragma include` directives
  159. handle_includes(source, resource_manager);
  160. // Inject `DEBUG` or `NDEBUG` macro definitions
  161. inject_debug_macro(source);
  162. // Detect declared shader types via the `#pragma vertex`, `#pragma fragment` and `#pragma geometry` directives
  163. std::list<shader_type> shader_types = detect_shader_types(source);
  164. // Load detected shaders
  165. std::list<shader*> shaders;
  166. for (shader_type type: shader_types)
  167. {
  168. text_file type_source = *source;
  169. inject_shader_type_macro(&type_source, type);
  170. std::string source_buffer = generate_source_buffer(type_source);
  171. shaders.push_back(new shader(type, source_buffer));
  172. }
  173. // Create shader program
  174. shader_program* program = new shader_program(shaders);
  175. // Delete shaders
  176. for (shader* shader: shaders)
  177. {
  178. delete shader;
  179. }
  180. return program;
  181. }