Antkeeper  0.0.1
shader-template.cpp
Go to the documentation of this file.
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 
21 #include <algorithm>
28 #include <sstream>
29 #include <unordered_set>
30 
31 namespace gl {
32 
34  m_template_source{source_code}
35 {
36  find_directives();
37  rehash();
38 }
39 
41  m_template_source{source_code}
42 {
43  find_directives();
44  rehash();
45 }
46 
47 shader_template::shader_template(text_file&& source_code, std::vector<std::shared_ptr<text_file>>&& include_files):
48  m_template_source{source_code},
49  m_include_files{include_files}
50 {
51  find_directives();
52  rehash();
53 }
54 
55 void shader_template::source(const text_file& source_code)
56 {
57  m_template_source = source_code;
58  m_include_files.clear();
59  find_directives();
60  rehash();
61 }
62 
64 {
65  m_template_source = source_code;
66  m_include_files.clear();
67  find_directives();
68  rehash();
69 }
70 
71 std::string shader_template::configure(gl::shader_stage stage, const dictionary_type& definitions) const
72 {
73  replace_stage_directives(stage);
74  replace_define_directives(definitions);
75 
76  // Join vector of source lines into single string
77  std::string string;
78  for (const auto& line: m_template_source.lines)
79  {
80  string += line;
81  string += '\n';
82  }
83 
84  return string;
85 }
86 
87 std::unique_ptr<gl::shader_object> shader_template::compile(gl::shader_stage stage, const dictionary_type& definitions) const
88 {
89  // Generate shader object source
90  const std::string object_source = configure(stage, definitions);
91 
92  // Create shader object
93  std::unique_ptr<gl::shader_object> object = std::make_unique<gl::shader_object>(stage);
94 
95  // Set shader object source
96  object->source(object_source);
97 
98  // Compile shader object
99  object->compile();
100 
101  return object;
102 }
103 
104 std::unique_ptr<gl::shader_program> shader_template::build(const dictionary_type& definitions) const
105 {
106  std::unique_ptr<gl::shader_object> vertex_object;
107  std::unique_ptr<gl::shader_object> fragment_object;
108  std::unique_ptr<gl::shader_object> geometry_object;
109 
110  // Create shader program
111  std::unique_ptr<gl::shader_program> program = std::make_unique<gl::shader_program>();
112 
113  if (has_vertex_directive())
114  {
115  // Compile vertex shader object and attach to shader program
116  vertex_object = compile(gl::shader_stage::vertex, definitions);
117  program->attach(*vertex_object);
118  }
119 
121  {
122  // Compile fragment shader object and attach to shader program
123  fragment_object = compile(gl::shader_stage::fragment, definitions);
124  program->attach(*fragment_object);
125  }
126 
128  {
129  // Compile geometry shader object and attach to shader program
130  geometry_object = compile(gl::shader_stage::geometry, definitions);
131  program->attach(*geometry_object);
132  }
133 
134  // Link attached shader objects into shader program
135  program->link();
136 
137  // Detach all attached shader objects
138  program->detach_all();
139 
140  return program;
141 }
142 
143 void shader_template::find_directives()
144 {
145  // Reset directives
146  m_vertex_directives.clear();
147  m_fragment_directives.clear();
148  m_geometry_directives.clear();
149  m_define_directives.clear();
150 
151  // Parse directives
152  for (std::size_t i = 0; i < m_template_source.lines.size(); ++i)
153  {
154  std::istringstream line_stream(m_template_source.lines[i]);
155  std::string token;
156 
157  // Detect `#pragma` directives
158  if (line_stream >> token && token == "#pragma")
159  {
160  if (line_stream >> token)
161  {
162  // Map line numbers of supported directives
163  if (token == "define")
164  {
165  if (line_stream >> token)
166  {
167  m_define_directives.insert({token, i});
168  }
169  }
170  else if (token == "vertex")
171  {
172  m_vertex_directives.insert(i);
173  }
174  else if (token == "fragment")
175  {
176  m_fragment_directives.insert(i);
177  }
178  else if (token == "geometry")
179  {
180  m_geometry_directives.insert(i);
181  }
182  }
183  }
184  }
185 }
186 
187 void shader_template::rehash()
188 {
189  m_hash = 0;
190  for (const auto& line: m_template_source.lines)
191  {
192  m_hash = hash_combine(m_hash, std::hash<std::string>{}(line));
193  }
194 }
195 
196 void shader_template::replace_stage_directives(gl::shader_stage stage) const
197 {
198  // Determine stage directives according to the shader stage being generated
199  const char* vertex_directive = (stage == gl::shader_stage::vertex) ? "#define __VERTEX__" : "/* #undef __VERTEX__ */";
200  const char* fragment_directive = (stage == gl::shader_stage::fragment) ? "#define __FRAGMENT__" : "/* #undef __FRAGMENT__ */";
201  const char* geometry_directive = (stage == gl::shader_stage::geometry) ? "#define __GEOMETRY__" : "/* #undef __GEOMETRY__ */";
202 
203  // Handle `#pragma <stage>` directives
204  for (const auto directive_line: m_vertex_directives)
205  {
206  m_template_source.lines[directive_line] = vertex_directive;
207  }
208  for (const auto directive_line: m_fragment_directives)
209  {
210  m_template_source.lines[directive_line] = fragment_directive;
211  }
212  for (const auto directive_line: m_geometry_directives)
213  {
214  m_template_source.lines[directive_line] = geometry_directive;
215  }
216 }
217 
218 void shader_template::replace_define_directives(const dictionary_type& definitions) const
219 {
220  // For each `#pragma define <key>` directive
221  for (const auto& define_directive: m_define_directives)
222  {
223  // Get a reference to the directive line
224  std::string& line = m_template_source.lines[define_directive.second];
225 
226  // Check if the corresponding definition was given by the configuration
227  auto definitions_it = definitions.find(define_directive.first);
228  if (definitions_it != definitions.end())
229  {
230  // Definition found, replace `#pragma define <key>` with `#define <key>` or `#define <key> <value>`
231  line = "#define " + define_directive.first;
232  if (!definitions_it->second.empty())
233  {
234  line += " " + definitions_it->second;
235  }
236  }
237  else
238  {
239  // Definition not found, replace `#pragma define <key>` with the comment `/* #undef <key> */`.
240  line = "/* #undef " + define_directive.first + " */";
241  }
242  }
243 }
244 
245 bool shader_template::has_define_directive(const std::string& key) const
246 {
247  return (m_define_directives.find(key) != m_define_directives.end());
248 }
249 
250 } // namespace gl
251 
259 static bool has_pragma_once(const text_file& source)
260 {
261  for (const auto& line: source.lines)
262  {
263  std::istringstream line_stream(line);
264  std::string token;
265 
266  // If line contains a `#pragma once` directive
267  if (line_stream >> token && token == "#pragma")
268  {
269  if (line_stream >> token && token == "once")
270  {
271  return true;
272  }
273  }
274  }
275 
276  return false;
277 }
278 
282 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)
283 {
284  // For each line in the source
285  for (std::size_t i = 0; i < source.lines.size(); ++i)
286  {
287  std::string token;
288  std::istringstream line_stream(source.lines[i]);
289 
290  // If line contains a `#pragma include` directive
291  if (line_stream >> token && token == "#pragma" &&
292  line_stream >> token && token == "include")
293  {
294  // If third token is enclosed in quotes or angled brackets
295  if (line_stream >> token && token.size() > 2 &&
296  ((token.front() == '\"' && token.back() == '\"') ||
297  (token.front() == '<' && token.back() == '>')))
298  {
299  // Extract include path
300  const std::filesystem::path path = token.substr(1, token.length() - 2);
301 
302  // Skip pre-included files that contain a `#pragma once` directive
303  if (include_once.contains(path))
304  {
305  source.lines[i] = "/* #pragma exclude " + token + " */";
306  continue;
307  }
308 
309  // Load include file
310  auto include_file = resource_manager.load<text_file>(path);
311  if (!include_file)
312  {
313  source.lines[i] = "#error file not found: " + path.string();
314  continue;
315  }
316  else
317  {
318  include_files.emplace_back(include_file);
319  }
320 
321  // If file has `#pragma once` directive
322  if (has_pragma_once(*include_file))
323  {
324  // Add file to set of files to include once
325  include_once.insert(path);
326  }
327 
328  // Create a copy of the include file
329  text_file include_file_copy = *include_file;
330 
331  // Handle `#pragma include` directives inside include file
332  handle_includes(include_files, include_file_copy, include_once, resource_manager);
333 
334  // Replace #pragma include directive with include file contents
335  source.lines.erase(source.lines.begin() + i);
336  source.lines.insert(source.lines.begin() + i, include_file_copy.lines.begin(), include_file_copy.lines.end());
337  i += include_file_copy.lines.size() - 1;
338  }
339  else
340  {
341  source.lines[i] = "#error malformed include directive: \"" + source.lines[i] + "\"";
342  }
343  }
344  }
345 }
346 
347 template <>
349 {
350  // Load shader template source file
351  const auto source_file = resource_loader<text_file>::load(resource_manager, ctx);
352 
353  // Make a copy of the shader template source file
354  text_file source_file_copy = *source_file;
355 
356  std::vector<std::shared_ptr<text_file>> include_files;
357 
358  // Handle `#pragma include` directives
359  std::unordered_set<std::filesystem::path> include_once;
360  include_once.insert(ctx.path());
361  handle_includes(include_files, source_file_copy, include_once, resource_manager);
362 
363  // Construct shader template
364  return std::make_unique<gl::shader_template>(std::move(source_file_copy), std::move(include_files));
365 }
std::string configure(gl::shader_stage stage, const dictionary_type &definitions={}) const
Configures shader object source code for a given shader stage and template dictionary.
std::unique_ptr< gl::shader_program > build(const dictionary_type &definitions={}) const
Configures and compiles shader objects, then links them into a shader program.
std::unordered_map< std::string, std::string > dictionary_type
Container of definitions used to generate #pragma define <key> <value> directives.
bool has_geometry_directive() const noexcept
Returns true if the template source contains one or more #pragma geometry directive.
constexpr shader_template() noexcept=default
Constructs an empty shader template.
bool has_define_directive(const std::string &key) const
Returns true if the template source contains one or more instance of #pragma define <key>.
bool has_vertex_directive() const noexcept
Returns true if the template source contains one or more #pragma vertex directive.
void source(const text_file &source_code)
Replaces the source code of the shader template.
std::unique_ptr< gl::shader_object > compile(gl::shader_stage stage, const dictionary_type &definitions={}) const
Configures and compiles a shader object.
bool has_fragment_directive() const noexcept
Returns true if the template source contains one or more #pragma fragment directive.
static std::unique_ptr< T > load(::resource_manager &resource_manager, deserialize_context &ctx)
Loads a resource.
Manages the loading, caching, and saving of resources.
std::shared_ptr< T > load(const std::filesystem::path &path)
Loads and caches a resource.
constexpr std::uint32_t hash_combine(std::uint32_t x, std::uint32_t y) noexcept
Combines two hash values.
hyperplane< T, 2 > line
2-dimensional hyperplane.
Definition: line.hpp:34
Graphics library interface.
Definition: window.hpp:28
shader_stage
Enumerates all supported shader stages.
@ fragment
Fragment shader stage.
@ vertex
Vertex shader stage.
@ geometry
Geometry shader stage.
Provides access to a deserialization state.
virtual const std::filesystem::path & path() const noexcept=0
Returns the path associated with this deserialize context.
Virtual text file.
Definition: text-file.hpp:30
std::vector< std::string > lines
Text file lines.
Definition: text-file.hpp:32