💿🐜 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.

365 lines
10 KiB

  1. /*
  2. * Copyright (C) 2023 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 <engine/gl/shader-template.hpp>
  20. #include <algorithm>
  21. #include <engine/gl/shader-object.hpp>
  22. #include <engine/gl/shader-program.hpp>
  23. #include <engine/resources/resource-loader.hpp>
  24. #include <engine/resources/resource-manager.hpp>
  25. #include <engine/utility/text-file.hpp>
  26. #include <engine/utility/hash/hash-combine.hpp>
  27. #include <sstream>
  28. #include <unordered_set>
  29. namespace gl {
  30. shader_template::shader_template(const text_file& source_code):
  31. m_template_source{source_code}
  32. {
  33. find_directives();
  34. rehash();
  35. }
  36. shader_template::shader_template(text_file&& source_code):
  37. m_template_source{source_code}
  38. {
  39. find_directives();
  40. rehash();
  41. }
  42. shader_template::shader_template(text_file&& source_code, std::vector<std::shared_ptr<text_file>>&& include_files):
  43. m_template_source{source_code},
  44. m_include_files{include_files}
  45. {
  46. find_directives();
  47. rehash();
  48. }
  49. void shader_template::source(const text_file& source_code)
  50. {
  51. m_template_source = source_code;
  52. m_include_files.clear();
  53. find_directives();
  54. rehash();
  55. }
  56. void shader_template::source(text_file&& source_code)
  57. {
  58. m_template_source = source_code;
  59. m_include_files.clear();
  60. find_directives();
  61. rehash();
  62. }
  63. std::string shader_template::configure(gl::shader_stage stage, const dictionary_type& definitions) const
  64. {
  65. replace_stage_directives(stage);
  66. replace_define_directives(definitions);
  67. // Join vector of source lines into single string
  68. std::string string;
  69. for (const auto& line: m_template_source.lines)
  70. {
  71. string += line;
  72. string += '\n';
  73. }
  74. return string;
  75. }
  76. std::unique_ptr<gl::shader_object> shader_template::compile(gl::shader_stage stage, const dictionary_type& definitions) const
  77. {
  78. // Generate shader object source
  79. const std::string object_source = configure(stage, definitions);
  80. // Create shader object
  81. std::unique_ptr<gl::shader_object> object = std::make_unique<gl::shader_object>(stage);
  82. // Set shader object source
  83. object->source(object_source);
  84. // Compile shader object
  85. object->compile();
  86. return object;
  87. }
  88. std::unique_ptr<gl::shader_program> shader_template::build(const dictionary_type& definitions) const
  89. {
  90. std::unique_ptr<gl::shader_object> vertex_object;
  91. std::unique_ptr<gl::shader_object> fragment_object;
  92. std::unique_ptr<gl::shader_object> geometry_object;
  93. // Create shader program
  94. std::unique_ptr<gl::shader_program> program = std::make_unique<gl::shader_program>();
  95. if (has_vertex_directive())
  96. {
  97. // Compile vertex shader object and attach to shader program
  98. vertex_object = compile(gl::shader_stage::vertex, definitions);
  99. program->attach(*vertex_object);
  100. }
  101. if (has_fragment_directive())
  102. {
  103. // Compile fragment shader object and attach to shader program
  104. fragment_object = compile(gl::shader_stage::fragment, definitions);
  105. program->attach(*fragment_object);
  106. }
  107. if (has_geometry_directive())
  108. {
  109. // Compile geometry shader object and attach to shader program
  110. geometry_object = compile(gl::shader_stage::geometry, definitions);
  111. program->attach(*geometry_object);
  112. }
  113. // Link attached shader objects into shader program
  114. program->link();
  115. // Detach all attached shader objects
  116. program->detach_all();
  117. return program;
  118. }
  119. void shader_template::find_directives()
  120. {
  121. // Reset directives
  122. m_vertex_directives.clear();
  123. m_fragment_directives.clear();
  124. m_geometry_directives.clear();
  125. m_define_directives.clear();
  126. // Parse directives
  127. for (std::size_t i = 0; i < m_template_source.lines.size(); ++i)
  128. {
  129. std::istringstream line_stream(m_template_source.lines[i]);
  130. std::string token;
  131. // Detect `#pragma` directives
  132. if (line_stream >> token && token == "#pragma")
  133. {
  134. if (line_stream >> token)
  135. {
  136. // Map line numbers of supported directives
  137. if (token == "define")
  138. {
  139. if (line_stream >> token)
  140. {
  141. m_define_directives.insert({token, i});
  142. }
  143. }
  144. else if (token == "vertex")
  145. {
  146. m_vertex_directives.insert(i);
  147. }
  148. else if (token == "fragment")
  149. {
  150. m_fragment_directives.insert(i);
  151. }
  152. else if (token == "geometry")
  153. {
  154. m_geometry_directives.insert(i);
  155. }
  156. }
  157. }
  158. }
  159. }
  160. void shader_template::rehash()
  161. {
  162. m_hash = 0;
  163. for (const auto& line: m_template_source.lines)
  164. {
  165. m_hash = hash_combine(m_hash, std::hash<std::string>{}(line));
  166. }
  167. }
  168. void shader_template::replace_stage_directives(gl::shader_stage stage) const
  169. {
  170. // Determine stage directives according to the shader stage being generated
  171. const char* vertex_directive = (stage == gl::shader_stage::vertex) ? "#define __VERTEX__" : "/* #undef __VERTEX__ */";
  172. const char* fragment_directive = (stage == gl::shader_stage::fragment) ? "#define __FRAGMENT__" : "/* #undef __FRAGMENT__ */";
  173. const char* geometry_directive = (stage == gl::shader_stage::geometry) ? "#define __GEOMETRY__" : "/* #undef __GEOMETRY__ */";
  174. // Handle `#pragma <stage>` directives
  175. for (const auto directive_line: m_vertex_directives)
  176. {
  177. m_template_source.lines[directive_line] = vertex_directive;
  178. }
  179. for (const auto directive_line: m_fragment_directives)
  180. {
  181. m_template_source.lines[directive_line] = fragment_directive;
  182. }
  183. for (const auto directive_line: m_geometry_directives)
  184. {
  185. m_template_source.lines[directive_line] = geometry_directive;
  186. }
  187. }
  188. void shader_template::replace_define_directives(const dictionary_type& definitions) const
  189. {
  190. // For each `#pragma define <key>` directive
  191. for (const auto& define_directive: m_define_directives)
  192. {
  193. // Get a reference to the directive line
  194. std::string& line = m_template_source.lines[define_directive.second];
  195. // Check if the corresponding definition was given by the configuration
  196. auto definitions_it = definitions.find(define_directive.first);
  197. if (definitions_it != definitions.end())
  198. {
  199. // Definition found, replace `#pragma define <key>` with `#define <key>` or `#define <key> <value>`
  200. line = "#define " + define_directive.first;
  201. if (!definitions_it->second.empty())
  202. {
  203. line += " " + definitions_it->second;
  204. }
  205. }
  206. else
  207. {
  208. // Definition not found, replace `#pragma define <key>` with the comment `/* #undef <key> */`.
  209. line = "/* #undef " + define_directive.first + " */";
  210. }
  211. }
  212. }
  213. bool shader_template::has_define_directive(const std::string& key) const
  214. {
  215. return (m_define_directives.find(key) != m_define_directives.end());
  216. }
  217. } // namespace gl
  218. /**
  219. * Scans a text file for the presence of a `#pragma once` directive.
  220. *
  221. * @param source Text file to scan.
  222. *
  223. * @return `true` if the file contains a `#pragma once` directive, `false` otherwise.
  224. */
  225. static bool has_pragma_once(const text_file& source)
  226. {
  227. for (const auto& line: source.lines)
  228. {
  229. std::istringstream line_stream(line);
  230. std::string token;
  231. // If line contains a `#pragma once` directive
  232. if (line_stream >> token && token == "#pragma")
  233. {
  234. if (line_stream >> token && token == "once")
  235. {
  236. return true;
  237. }
  238. }
  239. }
  240. return false;
  241. }
  242. /**
  243. * Handles `#pragma include` directives by loading the specified text files and inserting them in place.
  244. */
  245. static void handle_includes(std::vector<std::shared_ptr<text_file>>& include_files, text_file& source, std::unordered_set<std::filesystem::path>& include_once, resource_manager& resource_manager)
  246. {
  247. // For each line in the source
  248. for (std::size_t i = 0; i < source.lines.size(); ++i)
  249. {
  250. std::string token;
  251. std::istringstream line_stream(source.lines[i]);
  252. // If line contains a `#pragma include` directive
  253. if (line_stream >> token && token == "#pragma" &&
  254. line_stream >> token && token == "include")
  255. {
  256. // If third token is enclosed in quotes or angled brackets
  257. if (line_stream >> token && token.size() > 2 &&
  258. ((token.front() == '\"' && token.back() == '\"') ||
  259. (token.front() == '<' && token.back() == '>')))
  260. {
  261. // Extract include path
  262. const std::filesystem::path path = token.substr(1, token.length() - 2);
  263. // Skip pre-included files that contain a `#pragma once` directive
  264. if (include_once.contains(path))
  265. {
  266. source.lines[i] = "/* #pragma exclude " + token + " */";
  267. continue;
  268. }
  269. // Load include file
  270. auto include_file = resource_manager.load<text_file>(path);
  271. if (!include_file)
  272. {
  273. source.lines[i] = "#error file not found: " + path.string();
  274. continue;
  275. }
  276. else
  277. {
  278. include_files.emplace_back(include_file);
  279. }
  280. // If file has `#pragma once` directive
  281. if (has_pragma_once(*include_file))
  282. {
  283. // Add file to set of files to include once
  284. include_once.insert(path);
  285. }
  286. // Create a copy of the include file
  287. text_file include_file_copy = *include_file;
  288. // Handle `#pragma include` directives inside include file
  289. handle_includes(include_files, include_file_copy, include_once, resource_manager);
  290. // Replace #pragma include directive with include file contents
  291. source.lines.erase(source.lines.begin() + i);
  292. source.lines.insert(source.lines.begin() + i, include_file_copy.lines.begin(), include_file_copy.lines.end());
  293. i += include_file_copy.lines.size() - 1;
  294. }
  295. else
  296. {
  297. source.lines[i] = "#error malformed include directive: \"" + source.lines[i] + "\"";
  298. }
  299. }
  300. }
  301. }
  302. template <>
  303. std::unique_ptr<gl::shader_template> resource_loader<gl::shader_template>::load(::resource_manager& resource_manager, deserialize_context& ctx)
  304. {
  305. // Load shader template source file
  306. const auto source_file = resource_loader<text_file>::load(resource_manager, ctx);
  307. // Make a copy of the shader template source file
  308. text_file source_file_copy = *source_file;
  309. std::vector<std::shared_ptr<text_file>> include_files;
  310. // Handle `#pragma include` directives
  311. std::unordered_set<std::filesystem::path> include_once;
  312. include_once.insert(ctx.path());
  313. handle_includes(include_files, source_file_copy, include_once, resource_manager);
  314. // Construct shader template
  315. return std::make_unique<gl::shader_template>(std::move(source_file_copy), std::move(include_files));
  316. }