Loading...
Searching...
No Matches
ShaderManager.cpp
Go to the documentation of this file.
1//
2// Created by denzel on 13/08/2025.
3//
4#ifdef _WIN32
5#define NOMINMAX
6#define WIN32_LEAN_AND_MEAN
7#endif
8
9#include "hellfire/graphics/managers/ShaderManager.h"
10
11#include <iostream>
12#include <sstream>
13#include <stack>
14
15#include "hellfire/graphics/material/Material.h"
16#include "../backends/opengl/glsl.h"
17#include "hellfire/graphics/texture/Texture.h"
18
19#ifdef _WIN32
20#include <windows.h> // For GetModuleFileNameA and MAX_PATH
21#undef byte
22#endif
23
24namespace hellfire {
25 std::string ShaderManager::process_includes(const std::string &source, const std::string &base_path) {
26 std::string processed = source;
27 std::regex include_regex("#include\\s+\"([^\"]+)\"");
28 std::smatch match;
29
30 while (std::regex_search(processed, match, include_regex)) {
31 std::string include_relative_path = match[1].str();
32 std::string include_full_path = base_path + include_relative_path;
33
34 // Get the directory of the included file for nested includes
35 std::string include_dir = get_directory_from_path(include_full_path);
36
37 std::string include_content = load_include_file(include_full_path, include_dir);
38 processed = processed.replace(match.position(), match.length(), include_content);
39 }
40
41 return processed;
42 }
43
44 std::string ShaderManager::load_include_file(const std::string &path, const std::string &base_path) {
45 if (include_cache_.find(path) != include_cache_.end()) {
46 return include_cache_[path];
47 }
48
49 std::ifstream file(path);
50 if (!file.is_open()) {
51 throw std::runtime_error("Failed to open include file: " + path);
52 }
53
54 std::string content((std::istreambuf_iterator(file)),
55 std::istreambuf_iterator<char>());
56
57 // Process nested includes
58 content = process_includes(content, base_path);
59
60 // Cache the processed content
61 include_cache_[path] = content;
62 return clean_shader_content(content, base_path);
63 }
64
65 std::string ShaderManager::get_directory_from_path(const std::string &file_path) {
66 size_t last_slash = file_path.find_last_of("/\\");
67 if (last_slash != std::string::npos) {
68 return file_path.substr(0, last_slash + 1);
69 }
70 return "./";
71 }
72
73 std::string ShaderManager::process_defines(const std::string &source,
74 const std::unordered_set<std::string> &defines) {
75 std::istringstream stream(source);
76 std::ostringstream result;
77 std::string line;
78
79 stack<bool> condition_stack;
80 condition_stack.push(true); // Base condition is always true
81
82 while (std::getline(stream, line)) {
83 std::string trimmed = trim(line);
84
85 if (trimmed.substr(0, 7) == "#ifdef ") {
86 std::string define_name = trimmed.substr(7);
87 bool condition = defines.count(trim(define_name)) > 0;
88 condition_stack.push(condition_stack.top() && condition);
89 continue;
90 }
91
92 if (trimmed.substr(0, 8) == "#ifndef ") {
93 std::string define_name = trimmed.substr(8);
94 bool condition = defines.count(trim(define_name)) == 0;
95 condition_stack.push(condition_stack.top() && condition);
96 continue;
97 }
98
99 if (trimmed == "#endif") {
100 if (condition_stack.size() > 1) {
101 condition_stack.pop();
102 }
103 continue;
104 }
105
106 // Only include line if current condition is true
107 if (condition_stack.top()) {
108 result << line << "\n";
109 }
110 }
111
112 return result.str();
113 }
114
115 std::string ShaderManager::trim(const std::string &str) {
116 size_t start = str.find_first_not_of(" \t");
117 if (start == std::string::npos) return "";
118 size_t end = str.find_last_not_of(" \t");
119 return str.substr(start, end - start + 1);
120 }
121
122 std::string ShaderManager::load_shader_file(const std::string &path) {
123 // Handle path resolution (Windows executable path support)
124 std::string base_path;
125#ifdef _WIN32
126 char exePath[MAX_PATH];
127 GetModuleFileNameA(nullptr, exePath, MAX_PATH);
128 base_path = std::string(exePath);
129 base_path = base_path.substr(0, base_path.find_last_of("\\/")) + "/";
130#endif
131
132 std::string abs_path = path; // base_path + path
133 std::cout << "Loading shader from: " << abs_path << std::endl;
134
135 char *file_content = glsl::readFile(abs_path.c_str());
136 if (!file_content) {
137 throw std::runtime_error("Failed to read shader file: " + abs_path);
138 }
139
140 std::string content(file_content);
141 delete[] file_content;
142
143 return content;
144 }
145
146 std::string ShaderManager::ShaderVariant::get_key() const {
147 std::string key = vertex_path + "|" + fragment_path + "|";
148 for (const auto &define: defines) {
149 key += define + ",";
150 }
151 return key;
152 }
153
154 std::string ShaderManager::clean_shader_content(const std::string &content, const std::string &filepath) {
155 std::string cleaned = content;
156
157 // UTF-8 BOM (Byte Order Mask) is EF BB BF (shows as  in text)
158 std::string bom = "\xEF\xBB\xBF";
159
160 // Remove ALL occurrences of BOM, not just at the beginning
161 size_t pos = 0;
162 int removedCount = 0;
163 while ((pos = cleaned.find(bom, pos)) != std::string::npos) {
164 cleaned.erase(pos, bom.length());
165 removedCount++;
166 }
167
168 if (removedCount > 0) {
169 std::cout << "Removed " << removedCount << " BOM sequences from: " << filepath << std::endl;
170 }
171
172 return cleaned;
173 }
174
175 uint32_t ShaderManager::load_shader(const ShaderVariant &variant) {
176 std::string cache_key = variant.get_key();
177
178 // Check if already compiled
179 if (compiled_shaders_.find(cache_key) != compiled_shaders_.end()) {
180 return compiled_shaders_[cache_key];
181 }
182
183 try {
184 // Load and process vertex shader
185 std::string vertex_base_path = get_directory_from_path(variant.vertex_path);
186 std::string fragment_base_path = get_directory_from_path(variant.fragment_path);
187
188 // Load and process vertex shader
189 std::string vertex_source = load_shader_file(variant.vertex_path);
190 vertex_source = process_includes(vertex_source, vertex_base_path);
191 vertex_source = process_defines(vertex_source, variant.defines);
192
193 // Load and process fragment shader
194 std::string fragment_source = load_shader_file(variant.fragment_path);
195 fragment_source = process_includes(fragment_source, fragment_base_path);
196 fragment_source = process_defines(fragment_source, variant.defines);
197
198 // Compile shader
199 uint32_t program = compile_shader_program(vertex_source, fragment_source);
200
201 // Cache compiled shader
202 compiled_shaders_[cache_key] = program;
203
204 return program;
205 } catch (const std::exception &e) {
206 std::cerr << "Error loading shader: " << e.what() << std::endl;
207 return 0;
208 }
209 }
210
212 ShaderVariant variant;
213
214 // Check if material has custom shader
215 if (material.has_custom_shader()) {
216 const auto *shader_info = material.get_shader_info();
217 variant.vertex_path = shader_info->vertex_path;
218 variant.fragment_path = shader_info->fragment_path;
219 variant.defines = shader_info->defines;
220
221 // Add automatic defines based on properties
222 add_automatic_defines(material, variant.defines);
223 } else {
224 variant.vertex_path = "assets/shaders/standard.vert";
225 variant.fragment_path = "assets/shaders/phong.frag";
226
227 // Add automatic defines for built-in shaders
228 add_automatic_defines(material, variant.defines);
229 }
230
231 uint32_t shader_id = load_shader(variant);
232 material.set_compiled_shader_id(shader_id);
233 return shader_id;
234 }
235
236 uint32_t ShaderManager::get_shader(const std::string &key) const {
237 const auto it = compiled_shaders_.find(key);
238 return (it != compiled_shaders_.end()) ? it->second : 0;
239 }
240
241 void ShaderManager::add_automatic_defines(const Material &material, std::unordered_set<std::string> &defines) {
242 if (material.get_property<Texture *>("diffuseTexture", nullptr)) {
243 defines.insert("HAS_DIFFUSE_TEXTURE");
244 }
245 if (material.get_property<Texture *>("normalTexture", nullptr)) {
246 defines.insert("HAS_NORMAL_TEXTURE");
247 }
248 if (material.get_property<Texture *>("specularTexture", nullptr)) {
249 defines.insert("HAS_SPECULAR_TEXTURE");
250 }
251 if (material.get_property<Texture *>("emissionTexture", nullptr)) {
252 defines.insert("HAS_EMISSION_TEXTURE");
253 }
254 if (material.get_property<Texture *>("roughnessTexture", nullptr)) {
255 defines.insert("HAS_ROUGHNESS_TEXTURE");
256 }
257 if (material.get_property<Texture *>("metallicTexture", nullptr)) {
258 defines.insert("HAS_METALLIC_TEXTURE");
259 }
260 }
261
262 uint32_t ShaderManager::load_shader_from_files(const std::string &vertex_path, const std::string &fragment_path) {
263 try {
264 std::string vertex_source = load_shader_file(vertex_path);
265 std::string fragment_source = load_shader_file(fragment_path);
266
267 uint32_t program_id = compile_shader_program(vertex_source, fragment_source);
268
269 if (program_id != 0) {
270 // Track shader for cleanup (using file paths as key)
271 std::string cache_key = vertex_path + "|" + fragment_path;
272 compiled_shaders_[cache_key] = program_id;
273 }
274
275 return program_id;
276 } catch (const std::exception &e) {
277 std::cerr << "Error loading shaders: " << e.what() << std::endl;
278 return 0;
279 }
280 }
281
283 include_cache_.clear();
284
285 // Clean up compiled shaders
286 for (const auto &[key, shader_id]: compiled_shaders_) {
287 glDeleteProgram(shader_id);
288 }
289 compiled_shaders_.clear();
290 }
291
293 std::vector<uint32_t> shader_ids;
294 shader_ids.reserve(compiled_shaders_.size());
295 for (const auto &[key, id]: compiled_shaders_) {
296 shader_ids.push_back(id);
297 }
298 return shader_ids;
299 }
300
301 uint32_t ShaderManager::compile_shader_program(const std::string &vertex_source, const std::string &fragment_source) {
302 const char *vertex_src = vertex_source.c_str();
303 const char *fragment_src = fragment_source.c_str();
304
305 GLuint vertex_shader = glsl::makeVertexShader(vertex_src);
306 if (vertex_shader == 0) {
307 std::cerr << "Failed to compile vertex shader" << std::endl;
308 return 0;
309 }
310
311 GLuint fragment_shader = glsl::makeFragmentShader(fragment_src);
312 if (fragment_shader == 0) {
313 std::cerr << "Failed to compile fragment shader" << std::endl;
314 glDeleteShader(vertex_shader);
315 return 0;
316 }
317
318 uint32_t program_id = glsl::makeShaderProgram(vertex_shader, fragment_shader);
319
320 // Clean up individual shaders after linking
321 glDeleteShader(vertex_shader);
322 glDeleteShader(fragment_shader);
323
324 if (program_id == 0) {
325 std::cerr << "Failed to link shader program" << std::endl;
326 }
327
328 return program_id;
329 }
330}
Definition glsl.h:9
static char * readFile(const char *filename)
Definition glsl.cpp:7
void set_compiled_shader_id(uint32_t shader_id)
Definition Material.h:235
bool has_custom_shader() const
Definition Material.h:227
const ShaderInfo * get_shader_info() const
Definition Material.h:231
std::vector< uint32_t > get_all_shader_ids() const
std::string process_defines(const std::string &source, const std::unordered_set< std::string > &defines)
std::unordered_map< std::string, std::string > include_cache_
std::unordered_map< std::string, uint32_t > compiled_shaders_
uint32_t load_shader_from_files(const std::string &vertex_path, const std::string &fragment_path)
std::string trim(const std::string &str)
void add_automatic_defines(const Material &material, std::unordered_set< std::string > &defines)
uint32_t load_shader(const ShaderVariant &variant)
std::string get_directory_from_path(const std::string &file_path)
std::string process_includes(const std::string &source, const std::string &base_path="shaders/")
std::string clean_shader_content(const std::string &content, const std::string &filepath)
uint32_t compile_shader_program(const std::string &vertex_source, const std::string &fragment_source)
std::string load_shader_file(const std::string &path)
uint32_t get_shader_for_material(Material &material)
uint32_t get_shader(const std::string &key) const
std::string load_include_file(const std::string &path, const std::string &base_path)
std::unordered_set< std::string > defines