From 2e31d82fb257e60bdecaee6c63b7bfe005d8b00d Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sat, 28 May 2022 20:12:33 -0400 Subject: [PATCH] Loader improvements (#1378) * tfrag3 data for merc2 * dma hooks for merc2 * start designing merc2 opengl, seems like the simple approach will be the best here * before bone packing experiment * fix up bones.gc * use uniform buffer * speedup, fix faces and eyes * final fixes * first pass at loader updates, tie is still bad * temp * improved loader * run iop less often --- game/CMakeLists.txt | 3 +- game/common/player_rpc_types.h | 5 - .../graphics/opengl_renderer/BucketRenderer.h | 2 +- game/graphics/opengl_renderer/Loader.cpp | 732 ------------------ game/graphics/opengl_renderer/Loader.h | 109 --- .../opengl_renderer/background/Shrub.cpp | 2 +- .../opengl_renderer/background/Shrub.h | 2 +- .../opengl_renderer/background/Tfrag3.cpp | 2 +- .../opengl_renderer/background/Tfrag3.h | 2 +- .../opengl_renderer/background/Tie3.cpp | 2 +- .../opengl_renderer/background/Tie3.h | 2 +- .../opengl_renderer/foreground/Merc2.cpp | 2 +- .../opengl_renderer/foreground/Merc2.h | 6 +- .../opengl_renderer/loader/Loader.cpp | 410 ++++++++++ game/graphics/opengl_renderer/loader/Loader.h | 52 ++ .../opengl_renderer/loader/LoaderStages.cpp | 529 +++++++++++++ .../opengl_renderer/loader/LoaderStages.h | 19 + game/graphics/opengl_renderer/loader/common.h | 50 ++ game/sce/sif_ee.cpp | 4 +- game/system/iop_thread.cpp | 74 +- game/system/iop_thread.h | 2 +- 21 files changed, 1085 insertions(+), 926 deletions(-) delete mode 100644 game/graphics/opengl_renderer/Loader.cpp delete mode 100644 game/graphics/opengl_renderer/Loader.h create mode 100644 game/graphics/opengl_renderer/loader/Loader.cpp create mode 100644 game/graphics/opengl_renderer/loader/Loader.h create mode 100644 game/graphics/opengl_renderer/loader/LoaderStages.cpp create mode 100644 game/graphics/opengl_renderer/loader/LoaderStages.h create mode 100644 game/graphics/opengl_renderer/loader/common.h diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 487da4640d..cb54228fc7 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -98,6 +98,8 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/ocean/OceanNear_PS2.cpp graphics/opengl_renderer/ocean/OceanTexture.cpp graphics/opengl_renderer/ocean/OceanTexture_PC.cpp + graphics/opengl_renderer/loader/Loader.cpp + graphics/opengl_renderer/loader/LoaderStages.cpp graphics/opengl_renderer/BucketRenderer.cpp graphics/opengl_renderer/CollideMeshRenderer.cpp graphics/opengl_renderer/debug_gui.cpp @@ -105,7 +107,6 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/DirectRenderer2.cpp graphics/opengl_renderer/EyeRenderer.cpp graphics/opengl_renderer/dma_helpers.cpp - graphics/opengl_renderer/Loader.cpp graphics/opengl_renderer/MercProgram.cpp graphics/opengl_renderer/MercRenderer.cpp graphics/opengl_renderer/opengl_utils.cpp diff --git a/game/common/player_rpc_types.h b/game/common/player_rpc_types.h index 2c2d0f1fb5..562666e4ca 100644 --- a/game/common/player_rpc_types.h +++ b/game/common/player_rpc_types.h @@ -6,10 +6,5 @@ * Note that PLAY and PLAYER are different. */ -#ifndef JAK1_PLAYER_RPC_TYPES_H -#define JAK1_PLAYER_RPC_TYPES_H - constexpr int PLAYER_RPC_ID = 0xdeb1; constexpr int PLAYER_RPC_CHANNEL = 0; - -#endif // JAK1_PLAYER_RPC_TYPES_H diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 5e10581070..b6de247212 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -6,7 +6,7 @@ #include "game/graphics/opengl_renderer/Shader.h" #include "game/graphics/texture/TexturePool.h" #include "game/graphics/opengl_renderer/Profiler.h" -#include "game/graphics/opengl_renderer/Loader.h" +#include "game/graphics/opengl_renderer/loader/Loader.h" #include "game/graphics/opengl_renderer/buckets.h" struct LevelVis { diff --git a/game/graphics/opengl_renderer/Loader.cpp b/game/graphics/opengl_renderer/Loader.cpp deleted file mode 100644 index fe4697827f..0000000000 --- a/game/graphics/opengl_renderer/Loader.cpp +++ /dev/null @@ -1,732 +0,0 @@ -#include "Loader.h" -#include "common/util/Timer.h" -#include "common/util/FileUtil.h" -#include "common/util/compress.h" - -namespace { -std::string uppercase_string(const std::string& s) { - std::string result; - for (auto c : s) { - result.push_back(toupper(c)); - } - return result; -} -} // namespace - -Loader::Loader() { - m_loader_thread = std::thread(&Loader::loader_thread, this); -} - -Loader::~Loader() { - { - std::lock_guard lk(m_loader_mutex); - m_want_shutdown = true; - m_loader_cv.notify_all(); - } - m_loader_thread.join(); -} - -/*! - * Try to get a loaded level by name. It may fail and return nullptr. - * Getting a level will reset the counter for the level and prevent it from being kicked out - * for a little while. - * - * This is safe to call from the graphics thread - */ -const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name) { - std::unique_lock lk(m_loader_mutex); - const auto& existing = m_loaded_tfrag3_levels.find(level_name); - if (existing == m_loaded_tfrag3_levels.end()) { - return nullptr; - } else { - existing->second->frames_since_last_used = 0; - return existing->second.get(); - } -} - -/*! - * The game calls this to give the loader a hint on which levels we want. - * If the loader is not busy, it will begin loading the level. - * This should be called on every frame. - */ -void Loader::set_want_levels(const std::vector& levels) { - std::unique_lock lk(m_loader_mutex); - m_desired_levels = levels; - if (!m_level_to_load.empty()) { - // can't do anything, we're loading a level right now - return; - } - - if (!m_initializing_tfrag3_levels.empty()) { - // can't do anything, we're initializing a level right now - return; - } - - // loader isn't busy, try to load one of the requested levels. - for (auto& lev : levels) { - auto it = m_loaded_tfrag3_levels.find(lev); - if (it == m_loaded_tfrag3_levels.end()) { - // we haven't loaded it yet. Request this level to load and wake up the thread. - m_level_to_load = lev; - lk.unlock(); - m_loader_cv.notify_all(); - return; - } - } -} - -/*! - * Get all levels that are in memory and used very recently. - */ -std::vector Loader::get_in_use_levels() { - std::vector result; - std::unique_lock lk(m_loader_mutex); - - for (auto& lev : m_loaded_tfrag3_levels) { - if (lev.second->frames_since_last_used < 5) { - result.push_back(lev.second.get()); - } - } - return result; -} - -/*! - * Loader function that runs in a completely separate thread. - * This is used for file I/O and unpacking. - */ -void Loader::loader_thread() { - while (!m_want_shutdown) { - std::unique_lock lk(m_loader_mutex); - - // this will keep us asleep until we've got a level to load. - m_loader_cv.wait(lk, [&] { return !m_level_to_load.empty() || m_want_shutdown; }); - if (m_want_shutdown) { - return; - } - std::string lev = m_level_to_load; - // don't hold the lock while reading the file. - lk.unlock(); - - // simulate slower hard drive (so that the loader thread can lose to the game loads) - // std::this_thread::sleep_for(std::chrono::milliseconds(1500)); - - // load the fr3 file - Timer disk_timer; - auto data = file_util::read_binary_file( - file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))})); - double disk_load_time = disk_timer.getSeconds(); - - // the FR3 files are compressed - Timer decomp_timer; - auto decomp_data = compression::decompress_zstd(data.data(), data.size()); - double decomp_time = decomp_timer.getSeconds(); - - // Read back into the tfrag3::Level structure - Timer import_timer; - auto result = std::make_unique(); - Serializer ser(decomp_data.data(), decomp_data.size()); - result->serialize(ser); - double import_time = import_timer.getSeconds(); - - // and finally "unpack", which creates the vertex data we'll upload to the GPU - Timer unpack_timer; - for (auto& tie_tree : result->tie_trees) { - for (auto& tree : tie_tree) { - tree.unpack(); - } - } - for (auto& t_tree : result->tfrag_trees) { - for (auto& tree : t_tree) { - tree.unpack(); - } - } - - for (auto& shrub_tree : result->shrub_trees) { - shrub_tree.unpack(); - } - fmt::print( - "------------> Load from file: {:.3f}s, import {:.3f}s, decomp {:.3f}s unpack {:.3f}s\n", - disk_load_time, import_time, decomp_time, unpack_timer.getSeconds()); - - // grab the lock again - lk.lock(); - // move this level to "initializing" state. - m_initializing_tfrag3_levels[lev] = std::make_unique(); // reset load state - m_initializing_tfrag3_levels[lev]->level = std::move(result); - m_level_to_load = ""; - m_file_load_done_cv.notify_all(); - } -} - -/*! - * Load a "common" FR3 file that has non-level textures. - * This should be called during initialization, before any threaded loading goes on. - */ -void Loader::load_common(TexturePool& tex_pool, const std::string& name) { - auto data = - file_util::read_binary_file(file_util::get_file_path({fmt::format("assets/{}.fr3", name)})); - - auto decomp_data = compression::decompress_zstd(data.data(), data.size()); - Serializer ser(decomp_data.data(), decomp_data.size()); - m_common_level.level = std::make_unique(); - m_common_level.level->serialize(ser); - for (auto& tex : m_common_level.level->textures) { - m_common_level.textures.push_back(add_texture(tex_pool, tex, true)); - } - - Timer tim; - init_merc(tim, m_common_level); -} - -/*! - * Upload a texture to the GPU, and give it to the pool. - */ -u64 Loader::add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common) { - GLuint gl_tex; - glGenTextures(1, &gl_tex); - glBindTexture(GL_TEXTURE_2D, gl_tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, - tex.data.data()); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, gl_tex); - glGenerateMipmap(GL_TEXTURE_2D); - float aniso = 0.0f; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso); - if (tex.load_to_pool) { - TextureInput in; - in.debug_page_name = tex.debug_tpage_name; - in.debug_name = tex.debug_name; - in.w = tex.w; - in.h = tex.h; - in.gpu_texture = gl_tex; - in.common = is_common; - in.id = PcTextureId::from_combo_id(tex.combo_id); - in.src_data = (const u8*)tex.data.data(); - pool.give_texture(in); - } - - return gl_tex; -} - -bool Loader::init_tfrag(Timer& timer, LevelData& data) { - if (data.tfrag_load_done) { - return true; - } - - if (data.level->tfrag_trees.front().empty()) { - data.tfrag_load_done = true; - return true; - } - - if (!data.tfrag_opengl_created) { - for (int geo = 0; geo < tfrag3::TFRAG_GEOS; geo++) { - auto& in_trees = data.level->tfrag_trees[geo]; - for (auto& in_tree : in_trees) { - GLuint& tree_out = data.tfrag_vertex_data[geo].emplace_back(); - glGenBuffers(1, &tree_out); - glBindBuffer(GL_ARRAY_BUFFER, tree_out); - glBufferData(GL_ARRAY_BUFFER, - in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr, - GL_STATIC_DRAW); - } - } - data.tfrag_opengl_created = true; - return false; - } - - constexpr u32 CHUNK_SIZE = 32768; - u32 uploaded_bytes = 0; - - while (true) { - const auto& tree = data.level->tfrag_trees[data.tfrag_next_geo][data.tfrag_next_tree]; - u32 end_vert_in_tree = tree.unpacked.vertices.size(); - // the number of vertices we'd need to finish the tree right now - size_t num_verts_left_in_tree = end_vert_in_tree - data.tfrag_next_vert; - size_t start_vert_for_chunk; - size_t end_vert_for_chunk; - - bool complete_tree; - - if (num_verts_left_in_tree > CHUNK_SIZE) { - complete_tree = false; - // should only do partial - start_vert_for_chunk = data.tfrag_next_vert; - end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; - data.tfrag_next_vert += CHUNK_SIZE; - } else { - // should do all! - start_vert_for_chunk = data.tfrag_next_vert; - end_vert_for_chunk = end_vert_in_tree; - complete_tree = true; - } - - // glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao); - glBindBuffer(GL_ARRAY_BUFFER, - data.tfrag_vertex_data[data.tfrag_next_geo][data.tfrag_next_tree]); - u32 upload_size = (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex); - glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex), - upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); - uploaded_bytes += upload_size; - - if (complete_tree) { - // and move on to next tree - data.tfrag_next_vert = 0; - data.tfrag_next_tree++; - if (data.tfrag_next_tree >= data.level->tfrag_trees[data.tfrag_next_geo].size()) { - data.tfrag_next_tree = 0; - data.tfrag_next_geo++; - if (data.tfrag_next_geo >= tfrag3::TFRAG_GEOS) { - data.tfrag_load_done = true; - data.tfrag_next_tree = 0; - data.tfrag_next_geo = 0; - data.tfrag_next_vert = 0; - return true; - } - } - } - - if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) { - return false; - } - } -} - -bool Loader::init_shrub(Timer& timer, LevelData& data) { - if (data.shrub_load_done) { - return true; - } - - if (data.level->shrub_trees.empty()) { - data.shrub_load_done = true; - return true; - } - - if (!data.shrub_opengl_created) { - for (auto& in_tree : data.level->shrub_trees) { - GLuint& tree_out = data.shrub_vertex_data.emplace_back(); - glGenBuffers(1, &tree_out); - glBindBuffer(GL_ARRAY_BUFFER, tree_out); - glBufferData(GL_ARRAY_BUFFER, - in_tree.unpacked.vertices.size() * sizeof(tfrag3::ShrubGpuVertex), nullptr, - GL_STATIC_DRAW); - } - data.shrub_opengl_created = true; - return false; - } - - constexpr u32 CHUNK_SIZE = 32768; - u32 uploaded_bytes = 0; - - while (true) { - const auto& tree = data.level->shrub_trees[data.shrub_next_tree]; - u32 end_vert_in_tree = tree.unpacked.vertices.size(); - // the number of vertices we'd need to finish the tree right now - size_t num_verts_left_in_tree = end_vert_in_tree - data.shrub_next_vert; - size_t start_vert_for_chunk; - size_t end_vert_for_chunk; - - bool complete_tree; - - if (num_verts_left_in_tree > CHUNK_SIZE) { - complete_tree = false; - // should only do partial - start_vert_for_chunk = data.shrub_next_vert; - end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; - data.shrub_next_vert += CHUNK_SIZE; - } else { - // should do all! - start_vert_for_chunk = data.shrub_next_vert; - end_vert_for_chunk = end_vert_in_tree; - complete_tree = true; - } - - // glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao); - glBindBuffer(GL_ARRAY_BUFFER, data.shrub_vertex_data[data.shrub_next_tree]); - u32 upload_size = (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::ShrubGpuVertex); - glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::ShrubGpuVertex), - upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); - uploaded_bytes += upload_size; - - if (complete_tree) { - // and move on to next tree - data.shrub_next_vert = 0; - data.shrub_next_tree++; - if (data.shrub_next_tree >= data.level->shrub_trees.size()) { - data.shrub_load_done = true; - return true; - } - } - - if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) { - return false; - } - } -} - -bool Loader::init_tie(Timer& timer, LevelData& data) { - if (data.tie_load_done) { - return true; - } - - if (data.level->tie_trees.front().empty()) { - data.tie_load_done = true; - return true; - } - - if (!data.tie_opengl_created) { - for (int geo = 0; geo < tfrag3::TIE_GEOS; geo++) { - auto& in_trees = data.level->tie_trees[geo]; - for (auto& in_tree : in_trees) { - LevelData::TieOpenGL& tree_out = data.tie_data[geo].emplace_back(); - glGenBuffers(1, &tree_out.vertex_buffer); - glBindBuffer(GL_ARRAY_BUFFER, tree_out.vertex_buffer); - glBufferData(GL_ARRAY_BUFFER, - in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr, - GL_STATIC_DRAW); - } - } - data.tie_opengl_created = true; - return false; - } - - if (!data.tie_verts_done) { - constexpr u32 CHUNK_SIZE = 32768; - u32 uploaded_bytes = 0; - - while (true) { - const auto& tree = data.level->tie_trees[data.tie_next_geo][data.tie_next_tree]; - u32 end_vert_in_tree = tree.unpacked.vertices.size(); - // the number of vertices we'd need to finish the tree right now - size_t num_verts_left_in_tree = end_vert_in_tree - data.tie_next_vert; - size_t start_vert_for_chunk; - size_t end_vert_for_chunk; - - bool complete_tree; - - if (num_verts_left_in_tree > CHUNK_SIZE) { - complete_tree = false; - // should only do partial - start_vert_for_chunk = data.tie_next_vert; - end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; - data.tie_next_vert += CHUNK_SIZE; - } else { - // should do all! - start_vert_for_chunk = data.tie_next_vert; - end_vert_for_chunk = end_vert_in_tree; - complete_tree = true; - } - - // glBindVertexArray(m_trees[m_load_state.vert_geo][m_load_state.vert_tree].vao); - glBindBuffer(GL_ARRAY_BUFFER, - data.tie_data[data.tie_next_geo][data.tie_next_tree].vertex_buffer); - u32 upload_size = - (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex); - glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex), - upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); - uploaded_bytes += upload_size; - - if (complete_tree) { - // and move on to next tree - data.tie_next_vert = 0; - data.tie_next_tree++; - if (data.tie_next_tree >= data.level->tie_trees[data.tie_next_geo].size()) { - data.tie_next_tree = 0; - data.tie_next_geo++; - if (data.tie_next_geo >= tfrag3::TIE_GEOS) { - data.tie_verts_done = true; - data.tie_next_tree = 0; - data.tie_next_geo = 0; - data.tie_next_vert = 0; - return false; - } - } - } - - if (timer.getMs() > Loader::TIE_LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) { - return false; - } - } - } - - if (!data.tie_wind_indices_done) { - bool abort = false; - for (; data.tie_next_geo < tfrag3::TIE_GEOS; data.tie_next_geo++) { - auto& geo_trees = data.level->tie_trees[data.tie_next_geo]; - for (; data.tie_next_tree < geo_trees.size(); data.tie_next_tree++) { - if (abort) { - return false; - } - auto& in_tree = geo_trees[data.tie_next_tree]; - auto& out_tree = data.tie_data[data.tie_next_geo][data.tie_next_tree]; - size_t wind_idx_buffer_len = 0; - for (auto& draw : in_tree.instanced_wind_draws) { - wind_idx_buffer_len += draw.vertex_index_stream.size(); - } - if (wind_idx_buffer_len > 0) { - out_tree.has_wind = true; - glGenBuffers(1, &out_tree.wind_indices); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out_tree.wind_indices); - std::vector temp; - temp.resize(wind_idx_buffer_len); - u32 off = 0; - for (auto& draw : in_tree.instanced_wind_draws) { - memcpy(temp.data() + off, draw.vertex_index_stream.data(), - draw.vertex_index_stream.size() * sizeof(u32)); - off += draw.vertex_index_stream.size(); - } - - glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(), - GL_STATIC_DRAW); - abort = true; - } - } - data.tie_next_tree = 0; - } - - data.tie_wind_indices_done = true; - data.tie_load_done = true; - return true; - } - - return false; -} - -bool Loader::init_collide(Timer& /*timer*/, LevelData& data) { - Timer t; - glGenBuffers(1, &data.collide_vertices); - glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices); - glBufferData(GL_ARRAY_BUFFER, - data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex), - data.level->collision.vertices.data(), GL_STATIC_DRAW); - fmt::print("init_collide took {:.2f} ms\n", t.getMs()); - return true; -} - -bool Loader::init_merc(Timer& /*timer*/, LevelData& data) { - Timer t; - glGenBuffers(1, &data.merc_indices); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.merc_indices); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.level->merc_data.indices.size() * sizeof(u32), - data.level->merc_data.indices.data(), GL_STATIC_DRAW); - - glGenBuffers(1, &data.merc_vertices); - glBindBuffer(GL_ARRAY_BUFFER, data.merc_vertices); - glBufferData(GL_ARRAY_BUFFER, data.level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex), - data.level->merc_data.vertices.data(), GL_STATIC_DRAW); - - for (auto& model : data.level->merc_data.models) { - data.merc_model_lookup[model.name] = &model; - m_all_merc_models[model.name].push_back({&model, data.load_id, &data}); - } - fmt::print("init_merc took {:.2f} ms\n", t.getMs()); - return true; -} - -bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) { - // try to move level from initializing to initialized: - - constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 128; - - int bytes_this_run = 0; - int tex_this_run = 0; - if (data.textures.size() < data.level->textures.size()) { - std::unique_lock tpool_lock(texture_pool.mutex()); - while (data.textures.size() < data.level->textures.size()) { - auto& tex = data.level->textures[data.textures.size()]; - data.textures.push_back(add_texture(texture_pool, tex, false)); - bytes_this_run += tex.w * tex.h * 4; - tex_this_run++; - if (tex_this_run > 20) { - break; - } - if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME || timer.getMs() > SHARED_TEXTURE_LOAD_BUDGET) { - break; - } - } - } - return data.textures.size() == data.level->textures.size(); -} - -void Loader::update_blocking(TexturePool& tex_pool) { - fmt::print("NOTE: coming out of blackout on next frame, doing all loads now...\n"); - - bool missing_levels = true; - while (missing_levels) { - bool needs_run = true; - - while (needs_run) { - needs_run = false; - { - std::unique_lock lk(m_loader_mutex); - if (!m_level_to_load.empty()) { - m_file_load_done_cv.wait(lk, [&]() { return m_level_to_load.empty(); }); - } - } - } - - needs_run = true; - - while (needs_run) { - needs_run = false; - { - std::unique_lock lk(m_loader_mutex); - if (!m_initializing_tfrag3_levels.empty()) { - needs_run = true; - } - } - - if (needs_run) { - update(tex_pool); - } - } - - { - std::unique_lock lk(m_loader_mutex); - missing_levels = false; - for (auto& des : m_desired_levels) { - if (m_loaded_tfrag3_levels.find(des) == m_loaded_tfrag3_levels.end()) { - fmt::print("blackout loader doing additional level {}...\n", des); - missing_levels = true; - } - } - } - - if (missing_levels) { - set_want_levels(m_desired_levels); - } - } - - fmt::print("Blackout loads done. Current status:"); - std::unique_lock lk(m_loader_mutex); - for (auto& ld : m_loaded_tfrag3_levels) { - fmt::print(" {} is loaded.\n", ld.first); - } -} - -void Loader::update(TexturePool& texture_pool) { - Timer loader_timer; - - // only main thread can touch this. - for (auto& lev : m_loaded_tfrag3_levels) { - lev.second->frames_since_last_used++; - } - - bool did_gpu_stuff = false; - - // work on moving initializing to initialized. - { - // accessing initializing, should lock - std::unique_lock lk(m_loader_mutex); - // grab the first initializing level: - const auto& it = m_initializing_tfrag3_levels.begin(); - if (it != m_initializing_tfrag3_levels.end()) { - std::string name = it->first; - auto& lev = it->second; - it->second->load_id = m_id++; - - // we're the only place that erases, so it's okay to unlock and hold a reference - lk.unlock(); - if (!lev->tfrag_load_done) { - did_gpu_stuff = true; - } - if (upload_textures(loader_timer, *lev, texture_pool)) { - if (init_tie(loader_timer, *lev)) { - if (init_tfrag(loader_timer, *lev)) { - if (init_shrub(loader_timer, *lev)) { - if (init_collide(loader_timer, *lev)) { - if (init_merc(loader_timer, *lev)) { - // we're done! lock before removing from loaded. - lk.lock(); - - m_loaded_tfrag3_levels[name] = std::move(lev); - m_initializing_tfrag3_levels.erase(it); - } - } - } - } - } - } - } - } - - if (!did_gpu_stuff) { - // try to remove levels. - if (m_loaded_tfrag3_levels.size() >= 3) { - for (auto& lev : m_loaded_tfrag3_levels) { - if (lev.second->frames_since_last_used > 180) { - std::unique_lock lk(texture_pool.mutex()); - fmt::print("------------------------- PC unloading {}\n", lev.first); - for (size_t i = 0; i < lev.second->level->textures.size(); i++) { - auto& tex = lev.second->level->textures[i]; - if (tex.load_to_pool) { - texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id), - lev.second->textures.at(i)); - } - } - lk.unlock(); - for (auto tex : lev.second->textures) { - if (EXTRA_TEX_DEBUG) { - for (auto& slot : texture_pool.all_textures()) { - if (slot.source) { - ASSERT(slot.gpu_texture != tex); - } else { - ASSERT(slot.gpu_texture != tex); - } - } - } - - glBindTexture(GL_TEXTURE_2D, tex); - glDeleteTextures(1, &tex); - } - - for (auto& tie_geo : lev.second->tie_data) { - for (auto& tie_tree : tie_geo) { - glDeleteBuffers(1, &tie_tree.vertex_buffer); - if (tie_tree.has_wind) { - glDeleteBuffers(1, &tie_tree.wind_indices); - } - } - } - - for (auto& tfrag_geo : lev.second->tfrag_vertex_data) { - for (auto& tfrag_buff : tfrag_geo) { - glDeleteBuffers(1, &tfrag_buff); - } - } - - glDeleteBuffers(1, &lev.second->collide_vertices); - glDeleteBuffers(1, &lev.second->merc_vertices); - glDeleteBuffers(1, &lev.second->merc_indices); - - for (auto& model : lev.second->level->merc_data.models) { - auto& mercs = m_all_merc_models.at(model.name); - MercRef ref{&model, lev.second->load_id}; - auto it = std::find(mercs.begin(), mercs.end(), ref); - ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name)); - mercs.erase(it); - } - - m_loaded_tfrag3_levels.erase(lev.first); - break; - } - } - } - } - - if (loader_timer.getMs() > 5) { - fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs()); - } -} - -std::optional Loader::get_merc_model(const char* model_name) { - // don't think we need to lock here... - const auto& it = m_all_merc_models.find(model_name); - if (it != m_all_merc_models.end() && !it->second.empty()) { - // it->second.front().parent_level->frames_since_last_used = 0; - return it->second.front(); - } else { - return std::nullopt; - } -} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Loader.h b/game/graphics/opengl_renderer/Loader.h deleted file mode 100644 index 666035517c..0000000000 --- a/game/graphics/opengl_renderer/Loader.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "game/graphics/pipelines/opengl.h" -#include "game/graphics/texture/TexturePool.h" -#include "common/custom_data/Tfrag3Data.h" -#include "common/util/Timer.h" - -class Loader { - public: - static constexpr float TIE_LOAD_BUDGET = 1.5f; - static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f; - Loader(); - ~Loader(); - void update(TexturePool& tex_pool); - void update_blocking(TexturePool& tex_pool); - - struct LevelData { - std::unique_ptr level; - std::vector textures; - u64 load_id = 0; - - struct TieOpenGL { - GLuint vertex_buffer; - bool has_wind = false; - GLuint wind_indices; - }; - std::array, tfrag3::TIE_GEOS> tie_data; - std::array, tfrag3::TIE_GEOS> tfrag_vertex_data; - std::vector shrub_vertex_data; - GLuint collide_vertices; - - GLuint merc_vertices; - GLuint merc_indices; - std::unordered_map merc_model_lookup; - - // internal load state - bool tie_opengl_created = false; - bool tie_verts_done = false; - bool tie_wind_indices_done = false; - bool tie_load_done = false; - u32 tie_next_geo = 0; - u32 tie_next_tree = 0; - u32 tie_next_vert = 0; - - bool tfrag_opengl_created = false; - bool tfrag_load_done = false; - u32 tfrag_next_geo = 0; - u32 tfrag_next_tree = 0; - u32 tfrag_next_vert = 0; - - bool shrub_opengl_created = false; - bool shrub_load_done = false; - u32 shrub_next_tree = 0; - u32 shrub_next_vert = 0; - - int frames_since_last_used = 0; - }; - - struct MercRef { - const tfrag3::MercModel* model = nullptr; - u64 load_id = 0; - const LevelData* level = nullptr; - bool operator==(const MercRef& other) const { - return model == other.model && load_id == other.load_id; - } - }; - - const LevelData* get_tfrag3_level(const std::string& level_name); - std::optional get_merc_model(const char* model_name); - void load_common(TexturePool& tex_pool, const std::string& name); - void set_want_levels(const std::vector& levels); - std::vector get_in_use_levels(); - - private: - void loader_thread(); - u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common); - - bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool); - bool init_tie(Timer& timer, LevelData& data); - bool init_tfrag(Timer& timer, LevelData& data); - bool init_shrub(Timer& timer, LevelData& data); - bool init_collide(Timer& timer, LevelData& data); - bool init_merc(Timer& timer, LevelData& data); - - // used by game and loader thread - std::unordered_map> m_initializing_tfrag3_levels; - - LevelData m_common_level; - - std::string m_level_to_load; - - std::thread m_loader_thread; - std::mutex m_loader_mutex; - std::condition_variable m_loader_cv; - std::condition_variable m_file_load_done_cv; - bool m_want_shutdown = false; - uint64_t m_id = 0; - - // used only by game thread - std::unordered_map> m_loaded_tfrag3_levels; - - std::unordered_map> m_all_merc_models; - - std::vector m_desired_levels; -}; diff --git a/game/graphics/opengl_renderer/background/Shrub.cpp b/game/graphics/opengl_renderer/background/Shrub.cpp index 980c306323..b6fe012f26 100644 --- a/game/graphics/opengl_renderer/background/Shrub.cpp +++ b/game/graphics/opengl_renderer/background/Shrub.cpp @@ -61,7 +61,7 @@ void Shrub::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProf render_all_trees(settings, render_state, prof); } -void Shrub::update_load(const Loader::LevelData* loader_data) { +void Shrub::update_load(const LevelData* loader_data) { const tfrag3::Level* lev_data = loader_data->level.get(); // We changed level! discard_tree_cache(); diff --git a/game/graphics/opengl_renderer/background/Shrub.h b/game/graphics/opengl_renderer/background/Shrub.h index 5da8fc17df..438d9442f9 100644 --- a/game/graphics/opengl_renderer/background/Shrub.h +++ b/game/graphics/opengl_renderer/background/Shrub.h @@ -24,7 +24,7 @@ class Shrub : public BucketRenderer { void draw_debug_window() override; private: - void update_load(const Loader::LevelData* loader_data); + void update_load(const LevelData* loader_data); void discard_tree_cache(); struct Tree { diff --git a/game/graphics/opengl_renderer/background/Tfrag3.cpp b/game/graphics/opengl_renderer/background/Tfrag3.cpp index 70bfeb8017..1b6846e0a7 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/background/Tfrag3.cpp @@ -39,7 +39,7 @@ Tfrag3::~Tfrag3() { } void Tfrag3::update_load(const std::vector& tree_kinds, - const Loader::LevelData* loader_data) { + const LevelData* loader_data) { const auto* lev_data = loader_data->level.get(); discard_tree_cache(); for (int geom = 0; geom < GEOM_MAX; ++geom) { diff --git a/game/graphics/opengl_renderer/background/Tfrag3.h b/game/graphics/opengl_renderer/background/Tfrag3.h index 9bec90e393..24762a087f 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.h +++ b/game/graphics/opengl_renderer/background/Tfrag3.h @@ -44,7 +44,7 @@ class Tfrag3 { }; void update_load(const std::vector& tree_kinds, - const Loader::LevelData* loader_data); + const LevelData* loader_data); int lod() const { return Gfx::g_global_settings.lod_tfrag; } diff --git a/game/graphics/opengl_renderer/background/Tie3.cpp b/game/graphics/opengl_renderer/background/Tie3.cpp index 47d18b4189..30505e631b 100644 --- a/game/graphics/opengl_renderer/background/Tie3.cpp +++ b/game/graphics/opengl_renderer/background/Tie3.cpp @@ -14,7 +14,7 @@ Tie3::~Tie3() { discard_tree_cache(); } -void Tie3::update_load(const Loader::LevelData* loader_data) { +void Tie3::update_load(const LevelData* loader_data) { const tfrag3::Level* lev_data = loader_data->level.get(); m_wind_vectors.clear(); // We changed level! diff --git a/game/graphics/opengl_renderer/background/Tie3.h b/game/graphics/opengl_renderer/background/Tie3.h index 7da1bc1752..5449a5e6ac 100644 --- a/game/graphics/opengl_renderer/background/Tie3.h +++ b/game/graphics/opengl_renderer/background/Tie3.h @@ -40,7 +40,7 @@ class Tie3 : public BucketRenderer { int lod() const { return Gfx::g_global_settings.lod_tie; } private: - void update_load(const Loader::LevelData* loader_data); + void update_load(const LevelData* loader_data); void discard_tree_cache(); void render_tree_wind(int idx, int geom, diff --git a/game/graphics/opengl_renderer/foreground/Merc2.cpp b/game/graphics/opengl_renderer/foreground/Merc2.cpp index 5796d60504..6d2c96b04f 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.cpp +++ b/game/graphics/opengl_renderer/foreground/Merc2.cpp @@ -412,7 +412,7 @@ void Merc2::flush_pending_model(SharedRenderState* render_state, ScopedProfilerN return; } - const Loader::LevelData* lev = m_current_model->level; + const LevelData* lev = m_current_model->level; const tfrag3::MercModel* model = m_current_model->model; int bone_count = (model->max_bones + 31) & (~31); // todo diff --git a/game/graphics/opengl_renderer/foreground/Merc2.h b/game/graphics/opengl_renderer/foreground/Merc2.h index 17472d2d9b..7d29df4dd2 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.h +++ b/game/graphics/opengl_renderer/foreground/Merc2.h @@ -56,7 +56,7 @@ class Merc2 : public BucketRenderer { u32 alloc_bones(int count, float scale); - std::optional m_current_model = std::nullopt; + std::optional m_current_model = std::nullopt; u16 m_current_effect_enable_bits = 0; u16 m_current_ignore_alpha_bits = 0; @@ -120,7 +120,7 @@ class Merc2 : public BucketRenderer { }; struct LevelDrawBucket { - const Loader::LevelData* level = nullptr; + const LevelData* level = nullptr; std::vector draws; u32 next_free_draw = 0; @@ -140,4 +140,4 @@ class Merc2 : public BucketRenderer { u32 m_next_free_bone = 0; void flush_draw_buckets(SharedRenderState* render_state, ScopedProfilerNode& prof); -}; \ No newline at end of file +}; diff --git a/game/graphics/opengl_renderer/loader/Loader.cpp b/game/graphics/opengl_renderer/loader/Loader.cpp new file mode 100644 index 0000000000..eb344ba977 --- /dev/null +++ b/game/graphics/opengl_renderer/loader/Loader.cpp @@ -0,0 +1,410 @@ +#include "Loader.h" +#include "common/util/Timer.h" +#include "common/util/FileUtil.h" +#include "common/util/compress.h" +#include "game/graphics/opengl_renderer/loader/LoaderStages.h" + +namespace { +std::string uppercase_string(const std::string& s) { + std::string result; + for (auto c : s) { + result.push_back(toupper(c)); + } + return result; +} +} // namespace + +Loader::Loader() { + m_loader_thread = std::thread(&Loader::loader_thread, this); + m_loader_stages = make_loader_stages(); +} + +Loader::~Loader() { + { + std::lock_guard lk(m_loader_mutex); + m_want_shutdown = true; + m_loader_cv.notify_all(); + } + m_loader_thread.join(); +} + +/*! + * Try to get a loaded level by name. It may fail and return nullptr. + * Getting a level will reset the counter for the level and prevent it from being kicked out + * for a little while. + * + * This is safe to call from the graphics thread + */ +const LevelData* Loader::get_tfrag3_level(const std::string& level_name) { + std::unique_lock lk(m_loader_mutex); + const auto& existing = m_loaded_tfrag3_levels.find(level_name); + if (existing == m_loaded_tfrag3_levels.end()) { + return nullptr; + } else { + existing->second->frames_since_last_used = 0; + return existing->second.get(); + } +} + +/*! + * The game calls this to give the loader a hint on which levels we want. + * If the loader is not busy, it will begin loading the level. + * This should be called on every frame. + */ +void Loader::set_want_levels(const std::vector& levels) { + std::unique_lock lk(m_loader_mutex); + m_desired_levels = levels; + if (!m_level_to_load.empty()) { + // can't do anything, we're loading a level right now + return; + } + + if (!m_initializing_tfrag3_levels.empty()) { + // can't do anything, we're initializing a level right now + return; + } + + // loader isn't busy, try to load one of the requested levels. + for (auto& lev : levels) { + auto it = m_loaded_tfrag3_levels.find(lev); + if (it == m_loaded_tfrag3_levels.end()) { + // we haven't loaded it yet. Request this level to load and wake up the thread. + m_level_to_load = lev; + lk.unlock(); + m_loader_cv.notify_all(); + return; + } + } +} + +/*! + * Get all levels that are in memory and used very recently. + */ +std::vector Loader::get_in_use_levels() { + std::vector result; + std::unique_lock lk(m_loader_mutex); + + for (auto& lev : m_loaded_tfrag3_levels) { + if (lev.second->frames_since_last_used < 5) { + result.push_back(lev.second.get()); + } + } + return result; +} + +/*! + * Loader function that runs in a completely separate thread. + * This is used for file I/O and unpacking. + */ +void Loader::loader_thread() { + while (!m_want_shutdown) { + std::unique_lock lk(m_loader_mutex); + + // this will keep us asleep until we've got a level to load. + m_loader_cv.wait(lk, [&] { return !m_level_to_load.empty() || m_want_shutdown; }); + if (m_want_shutdown) { + return; + } + std::string lev = m_level_to_load; + // don't hold the lock while reading the file. + lk.unlock(); + + // simulate slower hard drive (so that the loader thread can lose to the game loads) + // std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + // load the fr3 file + Timer disk_timer; + auto data = file_util::read_binary_file( + file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))})); + double disk_load_time = disk_timer.getSeconds(); + + // the FR3 files are compressed + Timer decomp_timer; + auto decomp_data = compression::decompress_zstd(data.data(), data.size()); + double decomp_time = decomp_timer.getSeconds(); + + // Read back into the tfrag3::Level structure + Timer import_timer; + auto result = std::make_unique(); + Serializer ser(decomp_data.data(), decomp_data.size()); + result->serialize(ser); + double import_time = import_timer.getSeconds(); + + // and finally "unpack", which creates the vertex data we'll upload to the GPU + Timer unpack_timer; + for (auto& tie_tree : result->tie_trees) { + for (auto& tree : tie_tree) { + tree.unpack(); + } + } + for (auto& t_tree : result->tfrag_trees) { + for (auto& tree : t_tree) { + tree.unpack(); + } + } + + for (auto& shrub_tree : result->shrub_trees) { + shrub_tree.unpack(); + } + fmt::print( + "------------> Load from file: {:.3f}s, import {:.3f}s, decomp {:.3f}s unpack {:.3f}s\n", + disk_load_time, import_time, decomp_time, unpack_timer.getSeconds()); + + // grab the lock again + lk.lock(); + // move this level to "initializing" state. + m_initializing_tfrag3_levels[lev] = std::make_unique(); // reset load state + m_initializing_tfrag3_levels[lev]->level = std::move(result); + m_level_to_load = ""; + m_file_load_done_cv.notify_all(); + } +} + +/*! + * Load a "common" FR3 file that has non-level textures. + * This should be called during initialization, before any threaded loading goes on. + */ +void Loader::load_common(TexturePool& tex_pool, const std::string& name) { + auto data = + file_util::read_binary_file(file_util::get_file_path({fmt::format("assets/{}.fr3", name)})); + + auto decomp_data = compression::decompress_zstd(data.data(), data.size()); + Serializer ser(decomp_data.data(), decomp_data.size()); + m_common_level.level = std::make_unique(); + m_common_level.level->serialize(ser); + for (auto& tex : m_common_level.level->textures) { + m_common_level.textures.push_back(add_texture(tex_pool, tex, true)); + } + + Timer tim; + MercLoaderStage mls; + LoaderInput input; + input.tex_pool = &tex_pool; + input.mercs = &m_all_merc_models; + input.lev_data = &m_common_level; + bool done = false; + while (!done) { + done = mls.run(tim, input); + } +} + +bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) { + // try to move level from initializing to initialized: + + constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 128; + + int bytes_this_run = 0; + int tex_this_run = 0; + if (data.textures.size() < data.level->textures.size()) { + std::unique_lock tpool_lock(texture_pool.mutex()); + while (data.textures.size() < data.level->textures.size()) { + auto& tex = data.level->textures[data.textures.size()]; + data.textures.push_back(add_texture(texture_pool, tex, false)); + bytes_this_run += tex.w * tex.h * 4; + tex_this_run++; + if (tex_this_run > 20) { + break; + } + if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME || timer.getMs() > SHARED_TEXTURE_LOAD_BUDGET) { + break; + } + } + } + return data.textures.size() == data.level->textures.size(); +} + +void Loader::update_blocking(TexturePool& tex_pool) { + fmt::print("NOTE: coming out of blackout on next frame, doing all loads now...\n"); + + bool missing_levels = true; + while (missing_levels) { + bool needs_run = true; + + while (needs_run) { + needs_run = false; + { + std::unique_lock lk(m_loader_mutex); + if (!m_level_to_load.empty()) { + m_file_load_done_cv.wait(lk, [&]() { return m_level_to_load.empty(); }); + } + } + } + + needs_run = true; + + while (needs_run) { + needs_run = false; + { + std::unique_lock lk(m_loader_mutex); + if (!m_initializing_tfrag3_levels.empty()) { + needs_run = true; + } + } + + if (needs_run) { + update(tex_pool); + } + } + + { + std::unique_lock lk(m_loader_mutex); + missing_levels = false; + for (auto& des : m_desired_levels) { + if (m_loaded_tfrag3_levels.find(des) == m_loaded_tfrag3_levels.end()) { + fmt::print("blackout loader doing additional level {}...\n", des); + missing_levels = true; + } + } + } + + if (missing_levels) { + set_want_levels(m_desired_levels); + } + } + + fmt::print("Blackout loads done. Current status:"); + std::unique_lock lk(m_loader_mutex); + for (auto& ld : m_loaded_tfrag3_levels) { + fmt::print(" {} is loaded.\n", ld.first); + } +} + +void Loader::update(TexturePool& texture_pool) { + Timer loader_timer; + + // only main thread can touch this. + for (auto& lev : m_loaded_tfrag3_levels) { + lev.second->frames_since_last_used++; + } + + bool did_gpu_stuff = false; + + // work on moving initializing to initialized. + { + // accessing initializing, should lock + std::unique_lock lk(m_loader_mutex); + // grab the first initializing level: + const auto& it = m_initializing_tfrag3_levels.begin(); + if (it != m_initializing_tfrag3_levels.end()) { + did_gpu_stuff = true; + std::string name = it->first; + auto& lev = it->second; + if (it->second->load_id == UINT64_MAX) { + it->second->load_id = m_id++; + } + + // we're the only place that erases, so it's okay to unlock and hold a reference + lk.unlock(); + bool done = true; + LoaderInput loader_input; + loader_input.lev_data = lev.get(); + loader_input.mercs = &m_all_merc_models; + loader_input.tex_pool = &texture_pool; + + for (auto& stage : m_loader_stages) { + Timer stage_timer; + done = stage->run(loader_timer, loader_input); + if (stage_timer.getMs() > 5.f) { + fmt::print("stage {} took {:.2f} ms\n", stage->name(), stage_timer.getMs()); + } + if (!done) { + break; + } + } + + if (done) { + lk.lock(); + m_loaded_tfrag3_levels[name] = std::move(lev); + m_initializing_tfrag3_levels.erase(it); + + for (auto& stage : m_loader_stages) { + stage->reset(); + } + } + } + } + + if (!did_gpu_stuff) { + // try to remove levels. + Timer unload_timer; + if (m_loaded_tfrag3_levels.size() >= 3) { + for (auto& lev : m_loaded_tfrag3_levels) { + if (lev.second->frames_since_last_used > 180) { + std::unique_lock lk(texture_pool.mutex()); + fmt::print("------------------------- PC unloading {}\n", lev.first); + for (size_t i = 0; i < lev.second->level->textures.size(); i++) { + auto& tex = lev.second->level->textures[i]; + if (tex.load_to_pool) { + texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id), + lev.second->textures.at(i)); + } + } + lk.unlock(); + for (auto tex : lev.second->textures) { + if (EXTRA_TEX_DEBUG) { + for (auto& slot : texture_pool.all_textures()) { + if (slot.source) { + ASSERT(slot.gpu_texture != tex); + } else { + ASSERT(slot.gpu_texture != tex); + } + } + } + + glBindTexture(GL_TEXTURE_2D, tex); + glDeleteTextures(1, &tex); + } + + for (auto& tie_geo : lev.second->tie_data) { + for (auto& tie_tree : tie_geo) { + glDeleteBuffers(1, &tie_tree.vertex_buffer); + if (tie_tree.has_wind) { + glDeleteBuffers(1, &tie_tree.wind_indices); + } + } + } + + for (auto& tfrag_geo : lev.second->tfrag_vertex_data) { + for (auto& tfrag_buff : tfrag_geo) { + glDeleteBuffers(1, &tfrag_buff); + } + } + + glDeleteBuffers(1, &lev.second->collide_vertices); + glDeleteBuffers(1, &lev.second->merc_vertices); + glDeleteBuffers(1, &lev.second->merc_indices); + + for (auto& model : lev.second->level->merc_data.models) { + auto& mercs = m_all_merc_models.at(model.name); + MercRef ref{&model, lev.second->load_id}; + auto it = std::find(mercs.begin(), mercs.end(), ref); + ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name)); + mercs.erase(it); + } + + m_loaded_tfrag3_levels.erase(lev.first); + break; + } + } + } + + if (unload_timer.getMs() > 5.f) { + fmt::print("Unload took {:.2f}\n", unload_timer.getMs()); + } + } + + if (loader_timer.getMs() > 5) { + fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs()); + } +} + +std::optional Loader::get_merc_model(const char* model_name) { + // don't think we need to lock here... + const auto& it = m_all_merc_models.find(model_name); + if (it != m_all_merc_models.end() && !it->second.empty()) { + // it->second.front().parent_level->frames_since_last_used = 0; + return it->second.front(); + } else { + return std::nullopt; + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/loader/Loader.h b/game/graphics/opengl_renderer/loader/Loader.h new file mode 100644 index 0000000000..1db3d404d3 --- /dev/null +++ b/game/graphics/opengl_renderer/loader/Loader.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include "game/graphics/pipelines/opengl.h" +#include "game/graphics/texture/TexturePool.h" +#include "common/custom_data/Tfrag3Data.h" +#include "common/util/Timer.h" +#include "game/graphics/opengl_renderer/loader/common.h" + +class Loader { + public: + static constexpr float TIE_LOAD_BUDGET = 1.5f; + static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f; + Loader(); + ~Loader(); + void update(TexturePool& tex_pool); + void update_blocking(TexturePool& tex_pool); + const LevelData* get_tfrag3_level(const std::string& level_name); + std::optional get_merc_model(const char* model_name); + void load_common(TexturePool& tex_pool, const std::string& name); + void set_want_levels(const std::vector& levels); + std::vector get_in_use_levels(); + + private: + void loader_thread(); + bool upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool); + + // used by game and loader thread + std::unordered_map> m_initializing_tfrag3_levels; + + LevelData m_common_level; + + std::string m_level_to_load; + + std::thread m_loader_thread; + std::mutex m_loader_mutex; + std::condition_variable m_loader_cv; + std::condition_variable m_file_load_done_cv; + bool m_want_shutdown = false; + uint64_t m_id = 0; + + // used only by game thread + std::unordered_map> m_loaded_tfrag3_levels; + + std::unordered_map> m_all_merc_models; + + std::vector m_desired_levels; + std::vector> m_loader_stages; +}; diff --git a/game/graphics/opengl_renderer/loader/LoaderStages.cpp b/game/graphics/opengl_renderer/loader/LoaderStages.cpp new file mode 100644 index 0000000000..e75e76c2a2 --- /dev/null +++ b/game/graphics/opengl_renderer/loader/LoaderStages.cpp @@ -0,0 +1,529 @@ +#include "Loader.h" +#include "LoaderStages.h" + +constexpr float LOAD_BUDGET = 2.5f; + +/*! + * Upload a texture to the GPU, and give it to the pool. + */ +u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common) { + GLuint gl_tex; + glGenTextures(1, &gl_tex); + glBindTexture(GL_TEXTURE_2D, gl_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + tex.data.data()); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gl_tex); + glGenerateMipmap(GL_TEXTURE_2D); + float aniso = 0.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso); + if (tex.load_to_pool) { + TextureInput in; + in.debug_page_name = tex.debug_tpage_name; + in.debug_name = tex.debug_name; + in.w = tex.w; + in.h = tex.h; + in.gpu_texture = gl_tex; + in.common = is_common; + in.id = PcTextureId::from_combo_id(tex.combo_id); + in.src_data = (const u8*)tex.data.data(); + pool.give_texture(in); + } + + return gl_tex; +} + +class TextureLoaderStage : public LoaderStage { + public: + TextureLoaderStage() : LoaderStage("texture") {} + bool run(Timer& timer, LoaderInput& data) override { + constexpr int MAX_TEX_BYTES_PER_FRAME = 1024 * 512; + + int bytes_this_run = 0; + int tex_this_run = 0; + if (data.lev_data->textures.size() < data.lev_data->level->textures.size()) { + std::unique_lock tpool_lock(data.tex_pool->mutex()); + while (data.lev_data->textures.size() < data.lev_data->level->textures.size()) { + auto& tex = data.lev_data->level->textures[data.lev_data->textures.size()]; + data.lev_data->textures.push_back(add_texture(*data.tex_pool, tex, false)); + bytes_this_run += tex.w * tex.h * 4; + tex_this_run++; + if (tex_this_run > 20) { + break; + } + if (bytes_this_run > MAX_TEX_BYTES_PER_FRAME || timer.getMs() > LOAD_BUDGET) { + break; + } + } + } + return data.lev_data->textures.size() == data.lev_data->level->textures.size(); + } + void reset() override {} +}; + +class TfragLoadStage : public LoaderStage { + public: + TfragLoadStage() : LoaderStage("tfrag") {} + bool run(Timer& timer, LoaderInput& data) override { + if (m_done) { + return true; + } + + if (data.lev_data->level->tfrag_trees.front().empty()) { + m_done = true; + return true; + } + + if (!m_opengl_created) { + for (int geo = 0; geo < tfrag3::TFRAG_GEOS; geo++) { + auto& in_trees = data.lev_data->level->tfrag_trees[geo]; + for (auto& in_tree : in_trees) { + GLuint& tree_out = data.lev_data->tfrag_vertex_data[geo].emplace_back(); + glGenBuffers(1, &tree_out); + glBindBuffer(GL_ARRAY_BUFFER, tree_out); + glBufferData(GL_ARRAY_BUFFER, + in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr, + GL_STATIC_DRAW); + } + } + m_opengl_created = true; + return false; + } + + constexpr u32 CHUNK_SIZE = 32768; + u32 uploaded_bytes = 0; + u32 unique_buffers = 0; + + while (true) { + const auto& tree = data.lev_data->level->tfrag_trees[m_next_geo][m_next_tree]; + u32 end_vert_in_tree = tree.unpacked.vertices.size(); + // the number of vertices we'd need to finish the tree right now + size_t num_verts_left_in_tree = end_vert_in_tree - m_next_vert; + size_t start_vert_for_chunk; + size_t end_vert_for_chunk; + + bool complete_tree; + + if (num_verts_left_in_tree > CHUNK_SIZE) { + complete_tree = false; + // should only do partial + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; + m_next_vert += CHUNK_SIZE; + } else { + // should do all! + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = end_vert_in_tree; + complete_tree = true; + } + + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->tfrag_vertex_data[m_next_geo][m_next_tree]); + u32 upload_size = + (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex); + glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex), + upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); + uploaded_bytes += upload_size; + + if (complete_tree) { + unique_buffers++; + // and move on to next tree + m_next_vert = 0; + m_next_tree++; + if (m_next_tree >= data.lev_data->level->tfrag_trees[m_next_geo].size()) { + m_next_tree = 0; + m_next_geo++; + if (m_next_geo >= tfrag3::TFRAG_GEOS) { + m_next_tree = true; + m_next_tree = 0; + m_next_geo = 0; + m_next_vert = 0; + m_done = true; + return true; + } + } + + return false; + } + + if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) { + return false; + } + } + } + + void reset() override { + m_done = false; + m_opengl_created = false; + m_next_geo = 0; + m_next_tree = 0; + m_next_vert = 0; + } + + private: + bool m_done = false; + bool m_opengl_created = false; + u32 m_next_geo = 0; + u32 m_next_tree = 0; + u32 m_next_vert = 0; +}; + +class ShrubLoadStage : public LoaderStage { + public: + ShrubLoadStage() : LoaderStage("shrub") {} + bool run(Timer& timer, LoaderInput& data) override { + if (m_done) { + return true; + } + + if (data.lev_data->level->shrub_trees.empty()) { + m_done = true; + return true; + } + + if (!m_opengl_created) { + for (auto& in_tree : data.lev_data->level->shrub_trees) { + GLuint& tree_out = data.lev_data->shrub_vertex_data.emplace_back(); + glGenBuffers(1, &tree_out); + glBindBuffer(GL_ARRAY_BUFFER, tree_out); + glBufferData(GL_ARRAY_BUFFER, + in_tree.unpacked.vertices.size() * sizeof(tfrag3::ShrubGpuVertex), nullptr, + GL_STATIC_DRAW); + } + m_opengl_created = true; + return false; + } + + constexpr u32 CHUNK_SIZE = 32768; + u32 uploaded_bytes = 0; + + while (true) { + const auto& tree = data.lev_data->level->shrub_trees[m_next_tree]; + u32 end_vert_in_tree = tree.unpacked.vertices.size(); + // the number of vertices we'd need to finish the tree right now + size_t num_verts_left_in_tree = end_vert_in_tree - m_next_vert; + size_t start_vert_for_chunk; + size_t end_vert_for_chunk; + + bool complete_tree; + + if (num_verts_left_in_tree > CHUNK_SIZE) { + complete_tree = false; + // should only do partial + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; + m_next_vert += CHUNK_SIZE; + } else { + // should do all! + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = end_vert_in_tree; + complete_tree = true; + } + + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->shrub_vertex_data[m_next_tree]); + u32 upload_size = + (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::ShrubGpuVertex); + glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::ShrubGpuVertex), + upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); + uploaded_bytes += upload_size; + + if (complete_tree) { + // and move on to next tree + m_next_vert = 0; + m_next_tree++; + if (m_next_tree >= data.lev_data->level->shrub_trees.size()) { + m_done = true; + return true; + } + } + + if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 128) > 2048) { + return false; + } + } + } + + void reset() override { + m_done = false; + m_opengl_created = false; + m_next_tree = 0; + m_next_vert = 0; + } + + private: + bool m_done = false; + bool m_opengl_created = false; + u32 m_next_tree = 0; + u32 m_next_vert = 0; +}; + +class TieLoadStage : public LoaderStage { + public: + TieLoadStage() : LoaderStage("tie") {} + bool run(Timer& timer, LoaderInput& data) override { + if (m_done) { + return true; + } + + if (data.lev_data->level->tie_trees.front().empty()) { + m_done = true; + return true; + } + + if (!m_opengl_created) { + for (int geo = 0; geo < tfrag3::TIE_GEOS; geo++) { + auto& in_trees = data.lev_data->level->tie_trees[geo]; + for (auto& in_tree : in_trees) { + LevelData::TieOpenGL& tree_out = data.lev_data->tie_data[geo].emplace_back(); + glGenBuffers(1, &tree_out.vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, tree_out.vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, + in_tree.unpacked.vertices.size() * sizeof(tfrag3::PreloadedVertex), nullptr, + GL_STATIC_DRAW); + } + } + m_opengl_created = true; + return false; + } + + if (!m_verts_done) { + constexpr u32 CHUNK_SIZE = 32768; + u32 uploaded_bytes = 0; + + while (true) { + const auto& tree = data.lev_data->level->tie_trees[m_next_geo][m_next_tree]; + u32 end_vert_in_tree = tree.unpacked.vertices.size(); + // the number of vertices we'd need to finish the tree right now + size_t num_verts_left_in_tree = end_vert_in_tree - m_next_vert; + size_t start_vert_for_chunk; + size_t end_vert_for_chunk; + + bool complete_tree; + + if (num_verts_left_in_tree > CHUNK_SIZE) { + complete_tree = false; + // should only do partial + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = start_vert_for_chunk + CHUNK_SIZE; + m_next_vert += CHUNK_SIZE; + } else { + // should do all! + start_vert_for_chunk = m_next_vert; + end_vert_for_chunk = end_vert_in_tree; + complete_tree = true; + } + + glBindBuffer(GL_ARRAY_BUFFER, + data.lev_data->tie_data[m_next_geo][m_next_tree].vertex_buffer); + u32 upload_size = + (end_vert_for_chunk - start_vert_for_chunk) * sizeof(tfrag3::PreloadedVertex); + glBufferSubData(GL_ARRAY_BUFFER, start_vert_for_chunk * sizeof(tfrag3::PreloadedVertex), + upload_size, tree.unpacked.vertices.data() + start_vert_for_chunk); + uploaded_bytes += upload_size; + + if (complete_tree) { + // and move on to next tree + m_next_vert = 0; + m_next_tree++; + if (m_next_tree >= data.lev_data->level->tie_trees[m_next_geo].size()) { + m_next_tree = 0; + m_next_geo++; + if (m_next_geo >= tfrag3::TIE_GEOS) { + m_verts_done = true; + m_next_tree = 0; + m_next_geo = 0; + m_next_vert = 0; + return false; + } + } + } + + if (timer.getMs() > LOAD_BUDGET || (uploaded_bytes / 1024) > 2048) { + return false; + } + } + } + + if (!m_wind_indices_done) { + bool abort = false; + for (; m_next_geo < tfrag3::TIE_GEOS; m_next_geo++) { + auto& geo_trees = data.lev_data->level->tie_trees[m_next_geo]; + for (; m_next_tree < geo_trees.size(); m_next_tree++) { + if (abort) { + return false; + } + auto& in_tree = geo_trees[m_next_tree]; + auto& out_tree = data.lev_data->tie_data[m_next_geo][m_next_tree]; + size_t wind_idx_buffer_len = 0; + for (auto& draw : in_tree.instanced_wind_draws) { + wind_idx_buffer_len += draw.vertex_index_stream.size(); + } + if (wind_idx_buffer_len > 0) { + out_tree.has_wind = true; + glGenBuffers(1, &out_tree.wind_indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, out_tree.wind_indices); + std::vector temp; + temp.resize(wind_idx_buffer_len); + u32 off = 0; + for (auto& draw : in_tree.instanced_wind_draws) { + memcpy(temp.data() + off, draw.vertex_index_stream.data(), + draw.vertex_index_stream.size() * sizeof(u32)); + off += draw.vertex_index_stream.size(); + } + + glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(), + GL_STATIC_DRAW); + abort = true; + } + } + m_next_tree = 0; + } + + m_indices_done = true; + m_done = true; + return true; + } + + return false; + } + + void reset() override { + m_done = false; + m_opengl_created = false; + m_next_geo = 0; + m_next_tree = 0; + m_next_vert = 0; + m_verts_done = false; + m_indices_done = false; + m_wind_indices_done = false; + } + + private: + bool m_done = false; + bool m_opengl_created = false; + bool m_verts_done = false; + bool m_indices_done = false; + bool m_wind_indices_done = false; + u32 m_next_geo = 0; + u32 m_next_tree = 0; + u32 m_next_vert = 0; +}; + +class CollideLoaderStage : public LoaderStage { + public: + CollideLoaderStage() : LoaderStage("collide") {} + bool run(Timer& /*timer*/, LoaderInput& data) override { + if (m_done) { + return true; + } + if (!m_opengl_created) { + glGenBuffers(1, &data.lev_data->collide_vertices); + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->collide_vertices); + glBufferData( + GL_ARRAY_BUFFER, + data.lev_data->level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex), + nullptr, GL_STATIC_DRAW); + m_opengl_created = true; + return false; + } + + u32 start = m_vtx; + u32 end = std::min((u32)data.lev_data->level->collision.vertices.size(), start + 32768); + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->collide_vertices); + glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(tfrag3::CollisionMesh::Vertex), + (end - start) * sizeof(tfrag3::CollisionMesh::Vertex), + data.lev_data->level->collision.vertices.data() + start); + m_vtx = end; + + if (m_vtx == data.lev_data->level->collision.vertices.size()) { + m_done = true; + return true; + } else { + return false; + } + } + void reset() override { + m_opengl_created = false; + m_vtx = 0; + m_done = false; + } + + private: + bool m_opengl_created = false; + u32 m_vtx = 0; + bool m_done = false; +}; + +MercLoaderStage::MercLoaderStage() : LoaderStage("merc") {} +void MercLoaderStage::reset() { + m_done = false; + m_opengl = false; + m_vtx_uploaded = false; + m_idx = 0; +} + +bool MercLoaderStage::run(Timer& /*timer*/, LoaderInput& data) { + if (m_done) { + return true; + } + + if (!m_opengl) { + glGenBuffers(1, &data.lev_data->merc_indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.lev_data->merc_indices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + data.lev_data->level->merc_data.indices.size() * sizeof(u32), nullptr, + GL_STATIC_DRAW); + + glGenBuffers(1, &data.lev_data->merc_vertices); + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_vertices); + glBufferData(GL_ARRAY_BUFFER, + data.lev_data->level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex), + nullptr, GL_STATIC_DRAW); + m_opengl = true; + } + + if (!m_vtx_uploaded) { + u32 start = m_idx; + m_idx = std::min(start + 32768, (u32)data.lev_data->level->merc_data.indices.size()); + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_indices); + glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(u32), (m_idx - start) * sizeof(u32), + data.lev_data->level->merc_data.indices.data() + start); + if (m_idx != data.lev_data->level->merc_data.indices.size()) { + return false; + } else { + m_idx = 0; + m_vtx_uploaded = true; + } + } + + u32 start = m_idx; + m_idx = std::min(start + 32768, (u32)data.lev_data->level->merc_data.vertices.size()); + glBindBuffer(GL_ARRAY_BUFFER, data.lev_data->merc_vertices); + glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(tfrag3::MercVertex), + (m_idx - start) * sizeof(tfrag3::MercVertex), + data.lev_data->level->merc_data.vertices.data() + start); + + if (m_idx != data.lev_data->level->merc_data.vertices.size()) { + return false; + } else { + m_done = true; + for (auto& model : data.lev_data->level->merc_data.models) { + data.lev_data->merc_model_lookup[model.name] = &model; + (*data.mercs)[model.name].push_back({&model, data.lev_data->load_id, data.lev_data}); + } + return true; + } + return true; +} + +std::vector> make_loader_stages() { + std::vector> ret; + ret.push_back(std::make_unique()); + ret.push_back(std::make_unique()); + ret.push_back(std::make_unique()); + ret.push_back(std::make_unique()); + ret.push_back(std::make_unique()); + ret.push_back(std::make_unique()); + return ret; +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/loader/LoaderStages.h b/game/graphics/opengl_renderer/loader/LoaderStages.h new file mode 100644 index 0000000000..a2e1f43e41 --- /dev/null +++ b/game/graphics/opengl_renderer/loader/LoaderStages.h @@ -0,0 +1,19 @@ +#pragma once + +#include "game/graphics/opengl_renderer/loader/common.h" + +std::vector> make_loader_stages(); +u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common); + +class MercLoaderStage : public LoaderStage { + public: + MercLoaderStage(); + bool run(Timer& timer, LoaderInput& data) override; + void reset() override; + + private: + bool m_done = false; + bool m_opengl = false; + bool m_vtx_uploaded = false; + u32 m_idx = 0; +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/loader/common.h b/game/graphics/opengl_renderer/loader/common.h new file mode 100644 index 0000000000..f64d848452 --- /dev/null +++ b/game/graphics/opengl_renderer/loader/common.h @@ -0,0 +1,50 @@ +#pragma once + +struct LevelData { + std::unique_ptr level; + std::vector textures; + u64 load_id = UINT64_MAX; + + struct TieOpenGL { + GLuint vertex_buffer; + bool has_wind = false; + GLuint wind_indices; + }; + std::array, tfrag3::TIE_GEOS> tie_data; + std::array, tfrag3::TIE_GEOS> tfrag_vertex_data; + std::vector shrub_vertex_data; + GLuint collide_vertices; + + GLuint merc_vertices; + GLuint merc_indices; + std::unordered_map merc_model_lookup; + + int frames_since_last_used = 0; +}; + +struct MercRef { + const tfrag3::MercModel* model = nullptr; + u64 load_id = 0; + const LevelData* level = nullptr; + bool operator==(const MercRef& other) const { + return model == other.model && load_id == other.load_id; + } +}; + +struct LoaderInput { + LevelData* lev_data; + TexturePool* tex_pool; + std::unordered_map>* mercs; +}; + +class LoaderStage { + public: + LoaderStage(const std::string& name) : m_name(name) {} + virtual bool run(Timer& timer, LoaderInput& data) = 0; + virtual void reset() = 0; + virtual ~LoaderStage() = default; + const std::string& name() const { return m_name; } + + protected: + std::string m_name; +}; \ No newline at end of file diff --git a/game/sce/sif_ee.cpp b/game/sce/sif_ee.cpp index 104c0747ff..bb659ea28b 100644 --- a/game/sce/sif_ee.cpp +++ b/game/sce/sif_ee.cpp @@ -81,12 +81,12 @@ s32 sceSifCallRpc(sceSifClientData* bd, ASSERT(!end_para); ASSERT(mode == 1); // async iop->kernel.sif_rpc(bd->rpcd.id, fno, mode, send, ssize, recv, rsize); - iop->signal_run_iop(); + iop->signal_run_iop(false); return 0; } s32 sceSifCheckStatRpc(sceSifRpcData* bd) { - iop->signal_run_iop(); + iop->signal_run_iop(false); return iop->kernel.sif_busy(bd->id); } diff --git a/game/system/iop_thread.cpp b/game/system/iop_thread.cpp index 2ed60fcdac..3529c2af93 100644 --- a/game/system/iop_thread.cpp +++ b/game/system/iop_thread.cpp @@ -68,76 +68,20 @@ void IOP::wait_run_iop() { void IOP::kill_from_ee() { want_exit = true; - signal_run_iop(); + signal_run_iop(true); } -void IOP::signal_run_iop() { +void IOP::signal_run_iop(bool force) { std::unique_lock lk(iters_mutex); - iop_iters_des++; // todo, tune this - if (iop_iters_des - iop_iters_act > 500) { - iop_iters_des = iop_iters_act + 500; + if (iop_iters_act == iop_iters_des || force) { + iop_iters_des++; // todo, tune this + if (iop_iters_des - iop_iters_act > 500) { + iop_iters_des = iop_iters_act + 500; + } + iop_run_cv.notify_all(); } - iop_run_cv.notify_all(); } IOP::~IOP() { reset_allocator(); -} - -// void launch_iop(SystemThreadInterface& interface) { -// IOP iop; -// -// printf("\n\n\n[IOP] Restart!\n"); -// iop.reset_allocator(); -// -//// dma_init_globals(); -//// iso_init_globals(); -//// fake_iso_init_globals(); -//// // iso_api -//// iso_cd_init_globals(); -//// iso_queue_init_globals(); -//// // isocommon -//// // overlord -//// ramdisk_init_globals(); -//// // sbank -//// // soundcommon -//// srpc_init_globals(); -// // ssound -// // stream -// -//// SCE_IOP::PS2_RegisterIOP(&iop); -//// PS2_RegisterIOP_EE(&iop); -// interface.initialization_complete(); -// -// printf("[IOP] Wait for OVERLORD to be started...\n"); -// iop.wait_for_overlord_start_cmd(); -// if(iop.status == IOP_OVERLORD_INIT) { -// printf("[IOP] Run!\n"); -// } else { -// printf("[IOP] shutdown!\n"); -// return; -// } -// -// iop.reset_allocator(); -// -// // init -//#ifdef ENABLE_OVERLORD -// start(iop.overlord_argc, iop.overlord_argv); -//#endif -// -// // unblock the EE, the overlord is set up! -// iop.signal_overlord_init_finish(); -// -// // IOP Kernel loop -// while(!interface.get_want_exit() && !iop.want_exit) { -// // the IOP kernel just runs at full blast, so we only run the IOP when the EE is waiting on -// the IOP. -// // Each time the EE is waiting on the IOP, it will run an iteration of the IOP kernel. -// iop.wait_run_iop(); -// iop.kernel.dispatchAll(); -// } -// -// // stop all threads in the iop kernel. -// // if the threads are not stopped nicely, we will deadlock on trying to destroy the kernel's -// condition variables. iop.kernel.shutdown(); -//} \ No newline at end of file +} \ No newline at end of file diff --git a/game/system/iop_thread.h b/game/system/iop_thread.h index bd42fc127c..668a091419 100644 --- a/game/system/iop_thread.h +++ b/game/system/iop_thread.h @@ -18,7 +18,7 @@ class IOP { void wait_for_overlord_start_cmd(); void wait_for_overlord_init_finish(); void signal_overlord_init_finish(); - void signal_run_iop(); + void signal_run_iop(bool force); void wait_run_iop(); void kill_from_ee();