A 3D game engine from scratch.
at main 672 lines 21 kB view raw
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}