A 3D game engine from scratch.
1// (c) 2020 Vlad-Stefan Harbuz <vlad@vladh.net>
2
3#include "../src_external/pstr.h"
4#include "shaders.hpp"
5#include "array.hpp"
6#include "util.hpp"
7#include "glutil.hpp"
8#include "logs.hpp"
9#include "files.hpp"
10#include "mats.hpp"
11#include "intrinsics.hpp"
12
13
14mats::State *mats::state = nullptr;
15
16
17Array<mats::Material> *
18mats::get_materials()
19{
20 return &mats::state->materials;
21}
22
23
24u32
25mats::get_n_materials()
26{
27 return mats::state->materials.length;
28}
29
30
31mats::Material *
32mats::push_material()
33{
34 return mats::state->materials.push();
35}
36
37
38void
39mats::mark_start_of_non_internal_materials()
40{
41 mats::state->first_non_internal_material_idx = mats::state->materials.length;
42}
43
44
45void
46mats::destroy_non_internal_materials()
47{
48 for (
49 u32 idx = mats::state->first_non_internal_material_idx;
50 idx < mats::state->materials.length;
51 idx++
52 ) {
53 destroy_material(mats::state->materials[idx]);
54 }
55
56 mats::state->materials.delete_elements_after_index(
57 mats::state->first_non_internal_material_idx);
58}
59
60
61bool
62mats::is_material_at_idx_internal(u32 idx)
63{
64 return idx < mats::state->first_non_internal_material_idx;
65}
66
67
68char const *
69mats::texture_type_to_string(TextureType texture_type) {
70 if (texture_type == TextureType::none) {
71 return "none";
72 } else if (texture_type == TextureType::albedo) {
73 return "albedo";
74 } else if (texture_type == TextureType::metallic) {
75 return "metallic";
76 } else if (texture_type == TextureType::roughness) {
77 return "roughness";
78 } else if (texture_type == TextureType::ao) {
79 return "ao";
80 } else if (texture_type == TextureType::normal) {
81 return "normal";
82 } else if (texture_type == TextureType::shadowmaps_2d) {
83 return "shadowmaps_2d";
84 } else if (texture_type == TextureType::shadowmaps_3d) {
85 return "shadowmaps_3d";
86 } else if (texture_type == TextureType::other) {
87 return "other";
88 } else if (texture_type == TextureType::g_position) {
89 return "g_position";
90 } else if (texture_type == TextureType::g_normal) {
91 return "g_normal";
92 } else if (texture_type == TextureType::g_albedo) {
93 return "g_albedo";
94 } else if (texture_type == TextureType::g_pbr) {
95 return "g_pbr";
96 } else if (texture_type == TextureType::l_color) {
97 return "l_color";
98 } else if (texture_type == TextureType::l_bright_color) {
99 return "l_bright_color";
100 } else if (texture_type == TextureType::l_depth) {
101 return "l_depth";
102 } else if (texture_type == TextureType::blur1) {
103 return "blur1";
104 } else if (texture_type == TextureType::blur2) {
105 return "blur2";
106 } else {
107 logs::warning("Could not convert TextureType to string: %d", texture_type);
108 return "<unknown>";
109 }
110}
111
112
113mats::TextureType
114mats::texture_type_from_string(char const *str)
115{
116 if (strcmp(str, "none") == 0) {
117 return TextureType::none;
118 } else if (strcmp(str, "albedo") == 0) {
119 return TextureType::albedo;
120 } else if (strcmp(str, "metallic") == 0) {
121 return TextureType::metallic;
122 } else if (strcmp(str, "roughness") == 0) {
123 return TextureType::roughness;
124 } else if (strcmp(str, "ao") == 0) {
125 return TextureType::ao;
126 } else if (strcmp(str, "normal") == 0) {
127 return TextureType::normal;
128 } else if (strcmp(str, "shadowmaps_2d") == 0) {
129 return TextureType::shadowmaps_2d;
130 } else if (strcmp(str, "shadowmaps_3d") == 0) {
131 return TextureType::shadowmaps_3d;
132 } else if (strcmp(str, "other") == 0) {
133 return TextureType::other;
134 } else if (strcmp(str, "g_position") == 0) {
135 return TextureType::g_position;
136 } else if (strcmp(str, "g_normal") == 0) {
137 return TextureType::g_normal;
138 } else if (strcmp(str, "g_albedo") == 0) {
139 return TextureType::g_albedo;
140 } else if (strcmp(str, "g_pbr") == 0) {
141 return TextureType::g_pbr;
142 } else if (strcmp(str, "l_color") == 0) {
143 return TextureType::l_color;
144 } else if (strcmp(str, "l_bright_color") == 0) {
145 return TextureType::l_bright_color;
146 } else if (strcmp(str, "l_depth") == 0) {
147 return TextureType::l_depth;
148 } else if (strcmp(str, "blur1") == 0) {
149 return TextureType::blur1;
150 } else if (strcmp(str, "blur2") == 0) {
151 return TextureType::blur2;
152 } else {
153 logs::warning("Could not parse TextureType from string: %s", str);
154 return TextureType::none;
155 }
156}
157
158
159void
160mats::activate_font_texture(u32 texture_name)
161{
162 glActiveTexture(GL_TEXTURE0);
163 glBindTexture(GL_TEXTURE_2D, texture_name);
164}
165
166
167void
168mats::push_font_texture(iv2 tex_coords, iv2 char_size, void const *data)
169{
170 glTexSubImage2D(GL_TEXTURE_2D, 0, tex_coords.x, tex_coords.y,
171 char_size.x, char_size.y,
172 GL_RED, GL_UNSIGNED_BYTE, data);
173}
174
175
176mats::Texture *
177mats::init_texture(
178 Texture *texture,
179 TextureType type,
180 char const *path
181) {
182 texture->type = type;
183 strcpy(texture->path, TEXTURE_DIR);
184 strcat(texture->path, path);
185 texture->target = GL_TEXTURE_2D;
186 texture->is_screensize_dependent = is_texture_type_screensize_dependent(type);
187 return texture;
188}
189
190
191mats::Texture *
192mats::init_texture(
193 Texture *texture,
194 GLenum target,
195 TextureType type,
196 u32 texture_name,
197 i32 width,
198 i32 height,
199 i32 n_components
200) {
201 texture->target = target;
202 texture->type = type;
203 texture->texture_name = texture_name;
204 texture->width = width;
205 texture->height = height;
206 texture->n_components = n_components;
207 texture->is_screensize_dependent = is_texture_type_screensize_dependent(type);
208 return texture;
209}
210
211
212void
213mats::destroy_texture(Texture *texture) {
214 logs::info("Destroying texture of type %s", texture_type_to_string(texture->type));
215 glDeleteTextures(1, &texture->texture_name);
216}
217
218
219mats::TextureAtlas *
220mats::init_texture_atlas(
221 TextureAtlas* atlas,
222 iv2 size
223) {
224 atlas->size = size;
225 atlas->next_position = iv2(0, 0);
226 atlas->max_allocated_position_per_axis = iv2(0, 0);
227
228 glGenTextures(1, &atlas->texture_name);
229 glBindTexture(GL_TEXTURE_2D, atlas->texture_name);
230 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, atlas->size.x, atlas->size.y, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
231 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
233 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
234 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
235 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
236
237 return atlas;
238}
239
240iv2
241mats::push_space_to_texture_atlas(TextureAtlas *atlas, iv2 space_size)
242{
243 // New space in a texture is first allocated along the x-axis.
244 // If we run over the end of the x-axis, we go to the next "row" from the
245 // beginning of the x-axis.
246 iv2 new_space_position = atlas->next_position;
247 iv2 new_space_end = atlas->next_position + space_size;
248
249 // If we run past the end of the y-axis, we've filled up the texture.
250 // This is a problem. We'll start reallocating from the beginning,
251 // overriding old stuff.
252 if (new_space_end.y > atlas->size.y) {
253 logs::error("Ran past y-axis end of TextureAtlas.");
254 // Maybe we just start overwriting stuff here.
255 new_space_position = iv2(0, 0);
256 }
257
258 // If we run past the end of the x-axis, move on to the next row.
259 if (new_space_end.x > atlas->size.x) {
260 new_space_position = iv2(0, atlas->max_allocated_position_per_axis.y);
261 }
262
263 atlas->max_allocated_position_per_axis = max(
264 atlas->max_allocated_position_per_axis, new_space_end
265 );
266 atlas->next_position = new_space_position + iv2(space_size.x, 0);
267 return new_space_position;
268}
269
270
271mats::Material *
272mats::init_material(Material *material, const char *name)
273{
274 // Hardcoded values for when we can't load a texture.
275 material->albedo_static = v4(-1.0f, -1.0f, -1.0f, -1.0f);
276 material->metallic_static = -1.0f;
277 material->roughness_static = -1.0f;
278 material->ao_static = -1.0f;
279
280 pstr_copy(material->name, MAX_COMMON_NAME_LENGTH, name);
281 material->state = MaterialState::initialized;
282 return material;
283}
284
285
286void
287mats::destroy_material(Material *material)
288{
289 shaders::destroy_shader_asset(&material->shader_asset);
290
291 if (shaders::is_shader_asset_valid(&material->depth_shader_asset)) {
292 shaders::destroy_shader_asset(&material->depth_shader_asset);
293 }
294
295 for (u32 idx = 0; idx < material->n_textures; idx++) {
296 Texture *texture = &material->textures[idx];
297 if (!texture->is_builtin) {
298 destroy_texture(texture);
299 }
300 }
301
302 memset(material, 0, sizeof(Material));
303}
304
305
306mats::Material *
307mats::get_material_by_name(char const *name)
308{
309 each (material, mats::state->materials) {
310 if (pstr_eq(material->name, name)) {
311 return material;
312 }
313 }
314 return nullptr;
315}
316
317
318void
319mats::add_texture_to_material(Material *material, Texture texture, const char *uniform_name)
320{
321 if (texture.is_screensize_dependent) {
322 material->is_screensize_dependent = true;
323 }
324 material->textures[material->n_textures++] = texture;
325 pstr_copy(material->texture_uniform_names[material->idx_texture_uniform_names++],
326 MAX_COMMON_NAME_LENGTH, uniform_name);
327}
328
329
330void
331mats::bind_texture_uniforms(Material *material)
332{
333 shaders::Asset *shader_asset = &material->shader_asset;
334
335 if (shader_asset->type != shaders::Type::depth) {
336 glUseProgram(shader_asset->program);
337
338 for (
339 u32 uniform_idx = 0;
340 uniform_idx < shader_asset->n_intrinsic_uniforms;
341 uniform_idx++
342 ) {
343 const char *uniform_name = shader_asset->intrinsic_uniform_names[uniform_idx];
344 if (strcmp(uniform_name, "should_use_normal_map") == 0) {
345 shaders::set_bool(shader_asset, "should_use_normal_map", material->should_use_normal_map);
346 } else if (strcmp(uniform_name, "albedo_static") == 0) {
347 shaders::set_vec4(shader_asset, "albedo_static", &material->albedo_static);
348 } else if (strcmp(uniform_name, "metallic_static") == 0) {
349 shaders::set_float(shader_asset, "metallic_static", material->metallic_static);
350 } else if (strcmp(uniform_name, "roughness_static") == 0) {
351 shaders::set_float(shader_asset, "roughness_static", material->roughness_static);
352 } else if (strcmp(uniform_name, "ao_static") == 0) {
353 shaders::set_float(shader_asset, "ao_static", material->ao_static);
354 }
355 }
356
357 shaders::reset_texture_units(shader_asset);
358
359 for (u32 idx = 0; idx < material->n_textures; idx++) {
360 Texture *texture = &material->textures[idx];
361 const char *uniform_name = material->texture_uniform_names[idx];
362 if (SETTINGS.shader_debug_on) {
363 logs::info("Setting uniforms: (uniform_name %s) (texture->texture_name %d)",
364 uniform_name, texture->texture_name);
365 }
366 shaders::set_int(shader_asset, uniform_name,
367 shaders::add_texture_unit(shader_asset, texture->texture_name, texture->target)
368 );
369 }
370 }
371
372 shader_asset->did_set_texture_uniforms = true;
373}
374
375
376void
377mats::delete_persistent_pbo()
378{
379 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
380 glDeleteBuffers(1, &mats::state->persistent_pbo.pbo);
381}
382
383
384void
385mats::init(
386 mats::State *materials_state,
387 memory::Pool *memory_pool
388) {
389 mats::state = materials_state;
390 mats::state->materials = Array<Material>(memory_pool, MAX_N_MATERIALS, "materials");
391 init_texture_name_pool(memory_pool, 256, 4);
392 init_persistent_pbo(25, 2048, 2048, 4);
393}
394
395
396bool
397mats::prepare_material_and_check_if_done(Material *material)
398{
399 if (material->state == MaterialState::empty) {
400 logs::warning("Empty material '%s'. This should never happen.", material->name);
401 return false;
402 }
403
404 if (material->state == MaterialState::initialized) {
405 // NOTE: We only have to copy stuff if we have one or more textures that
406 // don't have names yet.
407 bool should_try_to_copy_textures = false;
408 for (u32 idx = 0; idx < material->n_textures; idx++) {
409 Texture *texture = &material->textures[idx];
410 if (!texture->texture_name) {
411 should_try_to_copy_textures = true;
412 }
413 }
414
415 if (should_try_to_copy_textures) {
416 material->state = MaterialState::textures_being_copied_to_pbo;
417 tasks::push({
418 .fn = (tasks::TaskFn)copy_textures_to_pbo,
419 .argument_1 = (void*)material,
420 });
421 } else {
422 material->state = MaterialState::textures_copied_to_pbo;
423 }
424 }
425
426 if (material->state == MaterialState::textures_being_copied_to_pbo) {
427 // Wait. The task will progress our status.
428 }
429
430 if (material->state == MaterialState::textures_copied_to_pbo) {
431 generate_textures_from_pbo(material);
432 material->state = MaterialState::complete;
433 }
434
435 if (material->state == MaterialState::complete) {
436 // NOTE: Because the shader might be reloaded at any time, we need to
437 // check whether or not we need to set any uniforms every time.
438 if (!material->shader_asset.did_set_texture_uniforms) {
439 bind_texture_uniforms(material);
440 }
441
442 return true;
443 }
444
445 return false;
446}
447
448
449void
450mats::reload_shaders()
451{
452 memory::Pool temp_memory_pool = {};
453
454 each (material, mats::state->materials) {
455 shaders::load_shader_asset(&material->shader_asset, &temp_memory_pool);
456 if (shaders::is_shader_asset_valid(&material->depth_shader_asset)) {
457 shaders::load_shader_asset(&material->depth_shader_asset, &temp_memory_pool);
458 }
459 }
460
461 // NOTE: We don't reload the standard depth shader asset.
462 // It makes the code simpler and we don't really need to.
463
464 memory::destroy_memory_pool(&temp_memory_pool);
465}
466
467
468bool
469mats::is_texture_type_screensize_dependent(TextureType type)
470{
471 return (
472 type == TextureType::g_position ||
473 type == TextureType::g_normal ||
474 type == TextureType::g_albedo ||
475 type == TextureType::g_pbr ||
476 type == TextureType::l_color ||
477 type == TextureType::l_bright_color ||
478 type == TextureType::l_depth ||
479 type == TextureType::blur1 ||
480 type == TextureType::blur2
481 );
482}
483
484
485u16
486mats::get_new_persistent_pbo_idx()
487{
488 auto *ppbo = &mats::state->persistent_pbo;
489 u16 current_idx = ppbo->next_idx;
490 ppbo->next_idx++;
491 if (ppbo->next_idx >= ppbo->texture_count) {
492 ppbo->next_idx = 0;
493 }
494 return current_idx;
495}
496
497
498void *
499mats::get_memory_for_persistent_pbo_idx(u16 idx)
500{
501 auto *ppbo = &mats::state->persistent_pbo;
502 return (char*)ppbo->memory + ((u64)idx * ppbo->texture_size);
503}
504
505
506void
507mats::copy_textures_to_pbo(Material *material)
508{
509 for (u32 idx = 0; idx < material->n_textures; idx++) {
510 Texture *texture = &material->textures[idx];
511 if (texture->texture_name) {
512 continue;
513 }
514 unsigned char *image_data = files::load_image(texture->path, &texture->width, &texture->height,
515 &texture->n_components, true);
516 texture->pbo_idx_for_copy = get_new_persistent_pbo_idx();
517 memcpy(get_memory_for_persistent_pbo_idx(texture->pbo_idx_for_copy),
518 image_data, texture->width * texture->height * texture->n_components);
519 files::free_image(image_data);
520 }
521 material->state = MaterialState::textures_copied_to_pbo;
522}
523
524
525u32
526mats::get_new_texture_name(u32 target_size)
527{
528 auto *pool = &mats::state->texture_name_pool;
529 for (u32 idx_size = 0; idx_size < pool->n_sizes; idx_size++) {
530 if (pool->sizes[idx_size] == target_size) {
531 assert(pool->idx_next[idx_size] < pool->n_textures);
532 u32 idx_name = (idx_size * pool->n_textures) + pool->idx_next[idx_size];
533 u32 texture_name = pool->texture_names[idx_name];
534 pool->idx_next[idx_size]++;
535 return texture_name;
536 }
537 }
538 logs::fatal("Could not make texture of size %d as there is no pool for that size.", target_size);
539 return 0;
540}
541
542
543void *
544mats::get_offset_for_persistent_pbo_idx(u16 idx)
545{
546 return (void*)((u64)idx * mats::state->persistent_pbo.texture_size);
547}
548
549
550void
551mats::generate_textures_from_pbo(Material *material)
552{
553 if (material->have_textures_been_generated) {
554 logs::warning("Tried to generate textures but they've already been generated.");
555 }
556 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mats::state->persistent_pbo.pbo);
557
558 for (u32 idx = 0; idx < material->n_textures; idx++) {
559 Texture *texture = &material->textures[idx];
560 if (texture->texture_name != 0) {
561 continue;
562 }
563 texture->texture_name = get_new_texture_name(texture->width);
564 glBindTexture(GL_TEXTURE_2D, texture->texture_name);
565 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
566 glutil::get_texture_format_from_n_components(texture->n_components),
567 GL_UNSIGNED_BYTE,
568 get_offset_for_persistent_pbo_idx(texture->pbo_idx_for_copy));
569 glGenerateMipmap(GL_TEXTURE_2D);
570
571 if (texture->type == TextureType::normal) {
572 material->should_use_normal_map = true;
573 }
574 }
575
576 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
577 material->have_textures_been_generated = true;
578}
579
580
581char const *
582mats::material_state_to_string(MaterialState material_state)
583{
584 if (material_state == MaterialState::empty) {
585 return "empty";
586 } else if (material_state == MaterialState::initialized) {
587 return "initialized";
588 } else if (material_state == MaterialState::textures_being_copied_to_pbo) {
589 return "textures being copied to pbo";
590 } else if (material_state == MaterialState::textures_copied_to_pbo) {
591 return "textures copied to pbo";
592 } else if (material_state == MaterialState::complete) {
593 return "complete";
594 } else {
595 return "<unknown>";
596 }
597}
598
599
600mats::PersistentPbo *
601mats::init_persistent_pbo(
602 u16 texture_count, i32 width, i32 height, i32 n_components
603) {
604 auto *ppbo = &mats::state->persistent_pbo;
605 ppbo->texture_count = texture_count;
606 ppbo->width = width;
607 ppbo->height = height;
608 ppbo->n_components = n_components;
609 ppbo->texture_size = width * height * n_components;
610 ppbo->total_size = ppbo->texture_size * ppbo->texture_count;
611
612 glGenBuffers(1, &ppbo->pbo);
613 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, ppbo->pbo);
614
615 GLbitfield flags;
616 if (GLAD_GL_ARB_buffer_storage) {
617 flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
618 glBufferStorage(GL_PIXEL_UNPACK_BUFFER, ppbo->total_size, 0, flags);
619 } else {
620 flags = GL_MAP_WRITE_BIT;
621 glBufferData(GL_PIXEL_UNPACK_BUFFER, ppbo->total_size, 0, GL_DYNAMIC_DRAW);
622 }
623
624 ppbo->memory = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, ppbo->total_size, flags);
625
626 if (ppbo->memory == nullptr) {
627 logs::fatal("Could not get memory for PBO.");
628 }
629
630 // We need to unbind this or it will mess up some textures transfers
631 // after this function.
632 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
633
634 return ppbo;
635}
636
637
638mats::TextureNamePool *
639mats::init_texture_name_pool(
640 memory::Pool *memory_pool,
641 u32 n_textures,
642 u32 mipmap_max_level
643) {
644 auto *pool = &mats::state->texture_name_pool;
645 pool->n_textures = n_textures;
646 pool->mipmap_max_level = mipmap_max_level;
647 pool->n_sizes = 0;
648 pool->sizes[pool->n_sizes++] = 256;
649 pool->sizes[pool->n_sizes++] = 512;
650 pool->sizes[pool->n_sizes++] = 2048;
651
652 pool->texture_names = (u32*)memory::push(memory_pool,
653 sizeof(u32) * pool->n_textures * pool->n_sizes, "texture_names");
654
655 glGenTextures(pool->n_textures * pool->n_sizes, pool->texture_names);
656
657 range_named (idx_size, 0, pool->n_sizes) {
658 range_named (idx_texture, 0, pool->n_textures) {
659 u32 idx_name = (idx_size * pool->n_textures) + idx_texture;
660 glBindTexture(GL_TEXTURE_2D, pool->texture_names[idx_name]);
661 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
662 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
663 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
664 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
665 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, pool->mipmap_max_level);
666 glTexStorage2D(GL_TEXTURE_2D, pool->mipmap_max_level + 1, GL_RGBA8,
667 pool->sizes[idx_size], pool->sizes[idx_size]);
668 }
669 }
670
671 return pool;
672}