diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 7a170910ec..113aae95c4 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -4,6 +4,7 @@ #include "decompiler/level_extractor/BspHeader.h" #include "decompiler/level_extractor/extract_tfrag.h" #include "decompiler/level_extractor/extract_tie.h" +#include "common/util/compress.h" #include "common/util/FileUtil.h" namespace decompiler { @@ -107,8 +108,12 @@ void extract_from_level(ObjectFileDB& db, Serializer ser; tfrag_level.serialize(ser); + auto compressed = + compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second); + fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(), + 100.f * compressed.size() / ser.get_save_result().second); file_util::write_binary_file(file_util::get_file_path({fmt::format( "assets/{}.fr3", dgo_name.substr(0, dgo_name.length() - 4))}), - ser.get_save_result().first, ser.get_save_result().second); + compressed.data(), compressed.size()); } } // namespace decompiler diff --git a/decompiler/level_extractor/extract_tie.cpp b/decompiler/level_extractor/extract_tie.cpp index a09ad46931..5d2904b37c 100644 --- a/decompiler/level_extractor/extract_tie.cpp +++ b/decompiler/level_extractor/extract_tie.cpp @@ -525,11 +525,6 @@ void update_proto_info(std::vector* out, } } - // just for debug - for (int g = 0; g < 4; g++) { - frag_info.point_sizes.push_back(proto.geometry[g].tie_fragments[frag_idx].point_ref.size()); - } - info.frags.push_back(std::move(frag_info)); } } diff --git a/game/graphics/opengl_renderer/Loader.cpp b/game/graphics/opengl_renderer/Loader.cpp index a0d9adfe83..19c8bcc6ce 100644 --- a/game/graphics/opengl_renderer/Loader.cpp +++ b/game/graphics/opengl_renderer/Loader.cpp @@ -1,6 +1,7 @@ #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) { @@ -13,23 +14,66 @@ std::string uppercase_string(const std::string& s) { } // namespace tfrag3::Level* Loader::get_tfrag3_level(const std::string& level_name) { + std::unique_lock lk(m_loader_mutex); const auto& existing = m_tfrag3_levels.find(level_name); if (existing == m_tfrag3_levels.end()) { - fmt::print("Loader needs to load tfrag3 level: {}\n", level_name); + if (m_level_to_load.empty()) { + fmt::print("[pc loader] starting load for {}\n", level_name); + m_level_to_load = level_name; + lk.unlock(); + m_loader_cv.notify_all(); + return nullptr; + } else { + return nullptr; + } + } else { + existing->second.frames_since_last_used = 0; + return existing->second.level.get(); + } +} + +void Loader::loader_thread() { + while (!m_want_shutdown) { + std::unique_lock lk(m_loader_mutex); + 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; + lk.unlock(); + Timer disk_timer; auto data = file_util::read_binary_file( - file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(level_name))})); + file_util::get_file_path({fmt::format("assets/{}.fr3", uppercase_string(lev))})); double disk_load_time = disk_timer.getSeconds(); + Timer decomp_timer; + auto decomp_data = compression::decompress_zstd(data.data(), data.size()); + double decomp_time = decomp_timer.getSeconds(); + Timer import_timer; - auto& result = m_tfrag3_levels[level_name]; - Serializer ser(data.data(), data.size()); - result.serialize(ser); + auto result = std::make_unique(); + Serializer ser(decomp_data.data(), decomp_data.size()); + result->serialize(ser); double import_time = import_timer.getSeconds(); - fmt::print("------------> Load from file: {:.3f}s, import {:.3f}s\n", disk_load_time, - import_time); - return &result; - } else { - return &existing->second; + fmt::print("------------> Load from file: {:.3f}s, import {:.3f}s, decomp {:.3f}s\n", + disk_load_time, import_time, decomp_time); + + lk.lock(); + m_level_to_load = ""; + m_tfrag3_levels[lev].level = std::move(result); } +} + +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(); } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Loader.h b/game/graphics/opengl_renderer/Loader.h index ab0560f458..b1f4d3e9d0 100644 --- a/game/graphics/opengl_renderer/Loader.h +++ b/game/graphics/opengl_renderer/Loader.h @@ -1,11 +1,30 @@ #pragma once +#include +#include +#include + #include "common/custom_data/Tfrag3Data.h" class Loader { public: + Loader(); + ~Loader(); tfrag3::Level* get_tfrag3_level(const std::string& level_name); private: - std::unordered_map m_tfrag3_levels; + struct Level { + std::unique_ptr level; + int frames_since_last_used = 0; + }; + + void loader_thread(); + + std::unordered_map m_tfrag3_levels; + std::string m_level_to_load; + + std::thread m_loader_thread; + std::mutex m_loader_mutex; + std::condition_variable m_loader_cv; + bool m_want_shutdown = false; }; diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp index d51fd3f31d..b2d570c443 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp @@ -27,6 +27,10 @@ Tfrag3::Tfrag3() { (void*)offsetof(DebugVertex, rgba) // offset (0) ); glBindVertexArray(0); + // regardless of how many we use some fixed max + // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so + // indexing works properly. + m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); } Tfrag3::~Tfrag3() { @@ -34,139 +38,203 @@ Tfrag3::~Tfrag3() { glDeleteVertexArrays(1, &m_debug_vao); } -void Tfrag3::setup_for_level(const std::vector& tree_kinds, +bool Tfrag3::update_load(const std::vector& tree_kinds, + const tfrag3::Level* lev_data) { + switch (m_load_state.state) { + case State::DISCARD_TREE: + discard_tree_cache(); + m_load_state.state = State::FREE_OLD_TREES; + break; + case State::FREE_OLD_TREES: + m_cached_trees.clear(); + m_load_state.state = State::INIT_NEW_TREES; + break; + case State::INIT_NEW_TREES: + + { + size_t time_of_day_count = 0; + size_t vis_temp_len = 0; + size_t max_draw = 0; + + for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees.size(); tree_idx++) { + size_t idx_buffer_len = 0; + + const auto& tree = lev_data->tfrag_trees[tree_idx]; + m_cached_trees.emplace_back(); + auto& tree_cache = m_cached_trees.back(); + + tree_cache.kind = tree.kind; + if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) { + max_draw = std::max(tree.draws.size(), max_draw); + for (auto& draw : tree.draws) { + idx_buffer_len += draw.vertex_index_stream.size(); + } + time_of_day_count = std::max(tree.colors.size(), time_of_day_count); + u32 verts = tree.vertices.size(); + glGenVertexArrays(1, &tree_cache.vao); + glBindVertexArray(tree_cache.vao); + glGenBuffers(1, &tree_cache.vertex_buffer); + tree_cache.vert_count = verts; + tree_cache.draws = &tree.draws; // todo - should we just copy this? + tree_cache.colors = &tree.colors; + tree_cache.vis = &tree.bvh; + tree_cache.tod_cache = swizzle_time_of_day(tree.colors); + vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); + glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr, + GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) + ); + + glVertexAttribIPointer( + 2, // location 2 in the shader + 1, // 1 values per vert + GL_UNSIGNED_SHORT, // u16 + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) + ); + + glGenBuffers(1, &tree_cache.index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, + GL_STREAM_DRAW); + tree_cache.index_list.resize(idx_buffer_len); + + glGenTextures(1, &tree_cache.time_of_day_texture); + glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture); + // just fill with zeros. this lets use use the faster texsubimage later + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8, m_color_result.data()); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindVertexArray(0); + } + } + + m_cache.vis_temp.resize(vis_temp_len); + m_cache.draw_idx_temp.resize(max_draw); + + assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); + m_load_state.state = UPLOAD_VERTS; + m_load_state.vert = 0; + } break; + + case State::UPLOAD_VERTS: { + constexpr u32 MAX_VERTS = 40000; + bool remaining = false; + for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) { + const auto& tree = lev_data->tfrag_trees[tree_idx]; + + if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) { + u32 verts = tree.vertices.size(); + u32 start_vert = (m_load_state.vert) * MAX_VERTS; + u32 end_vert = std::min(verts, (m_load_state.vert + 1) * MAX_VERTS); + if (end_vert > start_vert) { + glBindVertexArray(m_cached_trees[tree_idx].vao); + glBindBuffer(GL_ARRAY_BUFFER, m_cached_trees[tree_idx].vertex_buffer); + glBufferSubData(GL_ARRAY_BUFFER, start_vert * sizeof(tfrag3::PreloadedVertex), + (end_vert - start_vert) * sizeof(tfrag3::PreloadedVertex), + tree.vertices.data() + start_vert); + if (end_vert < verts) { + remaining = true; + } + } + } + } + m_load_state.vert++; + if (!remaining) { + m_load_state.state = INIT_TEX; + m_load_state.tex_id = 0; + } + } break; + + case State::INIT_TEX: + for (size_t max_tex = + std::min((size_t)m_load_state.tex_id + MAX_TEX_PER_FRAME, lev_data->textures.size()); + m_load_state.tex_id < max_tex; m_load_state.tex_id++) { + auto& tex = lev_data->textures[m_load_state.tex_id]; + 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); + m_textures.push_back(gl_tex); + } + return m_load_state.tex_id == lev_data->textures.size(); + break; + default: + assert(false); + } + + return false; +} + +bool Tfrag3::setup_for_level(const std::vector& tree_kinds, const std::string& level, SharedRenderState* render_state) { - // regardless of how many we use some fixed max - // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so - // indexing works properly. - m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); + // first, get the level in memory + Timer tfrag3_setup_timer; - // make sure we have the level data. auto lev_data = render_state->loader.get_tfrag3_level(level); - if (m_level_name != level) { - Timer tfrag3_setup_timer; - fmt::print("new level for tfrag3: {} -> {}\n", m_level_name, level); - fmt::print("discarding old stuff\n"); - discard_tree_cache(); - fmt::print("level has {} trees\n", lev_data->tfrag_trees.size()); - m_cached_trees.clear(); - - size_t time_of_day_count = 0; - size_t vis_temp_len = 0; - size_t max_draw = 0; - - for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees.size(); tree_idx++) { - size_t idx_buffer_len = 0; - - const auto& tree = lev_data->tfrag_trees[tree_idx]; - m_cached_trees.emplace_back(); - auto& tree_cache = m_cached_trees.back(); - - tree_cache.kind = tree.kind; - if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) { - max_draw = std::max(tree.draws.size(), max_draw); - for (auto& draw : tree.draws) { - idx_buffer_len += draw.vertex_index_stream.size(); - } - time_of_day_count = std::max(tree.colors.size(), time_of_day_count); - u32 verts = tree.vertices.size(); - fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts, - verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.draws.size()); - glGenVertexArrays(1, &tree_cache.vao); - glBindVertexArray(tree_cache.vao); - glGenBuffers(1, &tree_cache.vertex_buffer); - tree_cache.vert_count = verts; - tree_cache.draws = &tree.draws; // todo - should we just copy this? - tree_cache.colors = &tree.colors; - tree_cache.vis = &tree.bvh; - tree_cache.tod_cache = swizzle_time_of_day(tree.colors); - vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); - glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer); - glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr, - GL_STREAM_DRAW); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex), - tree.vertices.data()); - - glVertexAttribPointer(0, // location 0 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) - ); - - glVertexAttribPointer(1, // location 1 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) - ); - - glVertexAttribIPointer(2, // location 2 in the shader - 1, // 1 values per vert - GL_UNSIGNED_SHORT, // u16 - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) - ); - - glGenBuffers(1, &tree_cache.index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, - GL_STREAM_DRAW); - tree_cache.index_list.resize(idx_buffer_len); - - glGenTextures(1, &tree_cache.time_of_day_texture); - glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture); - // just fill with zeros. this lets use use the faster texsubimage later - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, - GL_UNSIGNED_INT_8_8_8_8, m_color_result.data()); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindVertexArray(0); - } - } - - fmt::print("TFRAG temporary vis output size: {}\n", vis_temp_len); - m_cache.vis_temp.resize(vis_temp_len); - fmt::print("TFRAG max draws/tree: {}\n", max_draw); - m_cache.draw_idx_temp.resize(max_draw); - - fmt::print("level has {} textures\n", lev_data->textures.size()); - for (auto& tex : lev_data->textures) { - GLuint gl_tex; - // fmt::print(" tex: {} x {} {} {}\n", tex.w, tex.h, tex.debug_name, tex.debug_tpage_name); - 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); - m_textures.push_back(gl_tex); - } - - fmt::print("level max time of day: {}\n", time_of_day_count); - assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); - - m_level_name = level; - fmt::print("TFRAG3 setup: {:.3f}\n", tfrag3_setup_timer.getSeconds()); + if (!lev_data) { + m_has_level = false; + return false; } + int init_load_state = m_load_state.state; + + if (m_level_name != level) { + m_has_level = false; + if (!m_load_state.loading) { + m_load_state.loading = true; + m_load_state.state = State::FIRST; + } + if (update_load(tree_kinds, lev_data)) { + m_has_level = true; + m_level_name = level; + m_load_state.loading = false; + } + + } else { + m_has_level = true; + } + if (tfrag3_setup_timer.getMs() > 5) { + fmt::print("TFRAG slow setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state); + } + + return m_has_level; } void Tfrag3::render_tree(const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { + if (!m_has_level) { + return; + } auto& tree = m_cached_trees.at(settings.tree_idx); assert(tree.kind != tfrag3::TFragmentTreeKind::INVALID); @@ -338,9 +406,6 @@ void Tfrag3::discard_tree_cache() { glDeleteVertexArrays(1, &tree.vao); } } - - // delete textures and stuff. - m_cached_trees.clear(); } namespace { diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.h b/game/graphics/opengl_renderer/tfrag/Tfrag3.h index f6168f8680..3f3135cc19 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.h +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.h @@ -29,7 +29,7 @@ class Tfrag3 { SharedRenderState* render_state, ScopedProfilerNode& prof); - void setup_for_level(const std::vector& tree_kinds, + bool setup_for_level(const std::vector& tree_kinds, const std::string& level, SharedRenderState* render_state); void discard_tree_cache(); @@ -44,6 +44,9 @@ class Tfrag3 { math::Vector4f rgba; }; + bool update_load(const std::vector& tree_kinds, + const tfrag3::Level* lev_data); + private: struct TreeCache { tfrag3::TFragmentTreeKind kind; @@ -93,5 +96,23 @@ class Tfrag3 { static constexpr int DEBUG_TRI_COUNT = 4096; std::vector m_debug_vert_data; + bool m_has_level = false; bool m_use_fast_time_of_day = true; + + enum State : u32 { + FIRST = 0, + DISCARD_TREE = 0, + FREE_OLD_TREES = 1, + INIT_NEW_TREES = 2, + UPLOAD_VERTS = 3, + INIT_TEX = 4, + }; + + struct { + bool loading = false; + State state; + u32 tex_id = 0; + u32 vert = 0; + } m_load_state; + static constexpr int MAX_TEX_PER_FRAME = 4; }; diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.cpp b/game/graphics/opengl_renderer/tfrag/Tie3.cpp index 7cb0ed4fe4..4e49b99c2b 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tie3.cpp @@ -2,177 +2,236 @@ #include "third-party/imgui/imgui.h" -Tie3::Tie3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) {} +Tie3::Tie3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) { + // regardless of how many we use some fixed max + // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so + // indexing works properly. + m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); +} Tie3::~Tie3() { discard_tree_cache(); } +bool Tie3::update_load(const tfrag3::Level* lev_data) { + switch (m_load_state.state) { + case DISCARD_TREE: + m_wind_vectors.clear(); + // We changed level! + discard_tree_cache(); + m_trees.resize(lev_data->tie_trees.size()); + m_load_state.state = INIT_NEW_TREES; + break; + case INIT_NEW_TREES: { + size_t time_of_day_count = 0; + size_t vis_temp_len = 0; + size_t max_draw = 0; + size_t max_idx_per_draw = 0; + u16 max_wind_idx = 0; + + // set up each tree + for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) { + size_t idx_buffer_len = 0; + size_t wind_idx_buffer_len = 0; + const auto& tree = lev_data->tie_trees[tree_idx]; + max_draw = std::max(tree.static_draws.size(), max_draw); + for (auto& draw : tree.static_draws) { + idx_buffer_len += draw.vertex_index_stream.size(); + max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size()); + } + for (auto& draw : tree.instanced_wind_draws) { + wind_idx_buffer_len += draw.vertex_index_stream.size(); + max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size()); + } + for (auto& inst : tree.instance_info) { + max_wind_idx = std::max(max_wind_idx, inst.wind_idx); + } + time_of_day_count = std::max(tree.colors.size(), time_of_day_count); + u32 verts = tree.vertices.size(); + fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts, + verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.static_draws.size()); + glGenVertexArrays(1, &m_trees[tree_idx].vao); + glBindVertexArray(m_trees[tree_idx].vao); + glGenBuffers(1, &m_trees[tree_idx].vertex_buffer); + m_trees[tree_idx].vert_count = verts; + m_trees[tree_idx].draws = &tree.static_draws; // todo - should we just copy this? + m_trees[tree_idx].colors = &tree.colors; + m_trees[tree_idx].vis = &tree.bvh; + m_trees[tree_idx].instance_info = &tree.instance_info; + m_trees[tree_idx].wind_draws = &tree.instanced_wind_draws; + vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); + m_trees[tree_idx].tod_cache = swizzle_time_of_day(tree.colors); + glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr, + GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + // glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex), + // tree.vertices.data()); + + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) + ); + + glVertexAttribIPointer(2, // location 2 in the shader + 1, // 1 values per vert + GL_UNSIGNED_SHORT, // u16 + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) + ); + + glGenBuffers(1, &m_trees[tree_idx].index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, + GL_STREAM_DRAW); + m_trees[tree_idx].index_list.resize(idx_buffer_len); + + if (wind_idx_buffer_len > 0) { + m_trees[tree_idx].wind_matrix_cache.resize(tree.instance_info.size()); + m_trees[tree_idx].has_wind = true; + glGenBuffers(1, &m_trees[tree_idx].wind_vertex_index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].wind_vertex_index_buffer); + std::vector temp; + temp.resize(wind_idx_buffer_len); + u32 off = 0; + for (auto& draw : tree.instanced_wind_draws) { + m_trees[tree_idx].wind_vertex_index_offsets.push_back(off); + 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); + } + + glActiveTexture(GL_TEXTURE10); + glGenTextures(1, &m_trees[tree_idx].time_of_day_texture); + glBindTexture(GL_TEXTURE_1D, m_trees[tree_idx].time_of_day_texture); + // just fill with zeros. this lets use use the faster texsubimage later + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8, m_color_result.data()); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glBindVertexArray(0); + } + + fmt::print("TIE temporary vis output size: {}\n", vis_temp_len); + m_cache.vis_temp.resize(vis_temp_len); + fmt::print("TIE max draws/tree: {}\n", max_draw); + m_cache.draw_idx_temp.resize(max_draw); + fmt::print("TIE draw with the most verts: {}\n", max_idx_per_draw); + fmt::print("wind: {}\n", max_wind_idx); + m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind. + fmt::print("level max time of day: {}\n", time_of_day_count); + assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); + } + m_load_state.state = UPLOAD_VERTS; + m_load_state.vert = 0; + break; + + case State::UPLOAD_VERTS: { + constexpr u32 MAX_VERTS = 40000; + bool remaining = false; + for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) { + const auto& tree = lev_data->tie_trees[tree_idx]; + u32 verts = tree.vertices.size(); + u32 start_vert = (m_load_state.vert) * MAX_VERTS; + u32 end_vert = std::min(verts, (m_load_state.vert + 1) * MAX_VERTS); + if (end_vert > start_vert) { + glBindVertexArray(m_trees[tree_idx].vao); + glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer); + glBufferSubData(GL_ARRAY_BUFFER, start_vert * sizeof(tfrag3::PreloadedVertex), + (end_vert - start_vert) * sizeof(tfrag3::PreloadedVertex), + tree.vertices.data() + start_vert); + if (end_vert < verts) { + remaining = true; + } + } + } + m_load_state.vert++; + if (!remaining) { + m_load_state.state = INIT_TEX; + m_load_state.tex = 0; + } + } break; + + case State::INIT_TEX: + for (size_t max_tex = std::min((size_t)m_load_state.tex + 3, lev_data->textures.size()); + m_load_state.tex < max_tex; m_load_state.tex++) { + auto& tex = lev_data->textures[m_load_state.tex]; + 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); + m_textures.push_back(gl_tex); + } + return m_load_state.tex == lev_data->textures.size(); + break; + default: + assert(false); + } + + return false; +} /*! * Set up all OpenGL and temporary buffers for a given level name. * The level name should be the 3 character short name. */ -void Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) { +bool Tie3::setup_for_level(const std::string& level, SharedRenderState* render_state) { // make sure we have the level data. // TODO: right now this will wait to load from disk and unpack it. + Timer tfrag3_setup_timer; auto lev_data = render_state->loader.get_tfrag3_level(level); - - // regardless of how many we use some fixed max - // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so - // indexing works properly. - m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); + if (!lev_data) { + return false; + } + int init_load_state = m_load_state.state; if (m_level_name != level) { - Timer tie_setup_timer; - m_wind_vectors.clear(); - // We changed level! - fmt::print("TIE3 level change! {} -> {}\n", m_level_name, level); - fmt::print(" Removing old level...\n"); - discard_tree_cache(); - fmt::print(" New level has {} tie trees\n", lev_data->tie_trees.size()); - m_trees.resize(lev_data->tie_trees.size()); - - size_t time_of_day_count = 0; - size_t vis_temp_len = 0; - size_t max_draw = 0; - size_t max_idx_per_draw = 0; - u16 max_wind_idx = 0; - - // set up each tree - for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees.size(); tree_idx++) { - size_t idx_buffer_len = 0; - size_t wind_idx_buffer_len = 0; - const auto& tree = lev_data->tie_trees[tree_idx]; - max_draw = std::max(tree.static_draws.size(), max_draw); - for (auto& draw : tree.static_draws) { - idx_buffer_len += draw.vertex_index_stream.size(); - max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size()); - } - for (auto& draw : tree.instanced_wind_draws) { - wind_idx_buffer_len += draw.vertex_index_stream.size(); - max_idx_per_draw = std::max(max_idx_per_draw, draw.vertex_index_stream.size()); - } - for (auto& inst : tree.instance_info) { - max_wind_idx = std::max(max_wind_idx, inst.wind_idx); - } - time_of_day_count = std::max(tree.colors.size(), time_of_day_count); - u32 verts = tree.vertices.size(); - fmt::print(" tree {} has {} verts ({} kB) and {} draws\n", tree_idx, verts, - verts * sizeof(tfrag3::PreloadedVertex) / 1024.f, tree.static_draws.size()); - glGenVertexArrays(1, &m_trees[tree_idx].vao); - glBindVertexArray(m_trees[tree_idx].vao); - glGenBuffers(1, &m_trees[tree_idx].vertex_buffer); - m_trees[tree_idx].vert_count = verts; - m_trees[tree_idx].draws = &tree.static_draws; // todo - should we just copy this? - m_trees[tree_idx].colors = &tree.colors; - m_trees[tree_idx].vis = &tree.bvh; - m_trees[tree_idx].instance_info = &tree.instance_info; - m_trees[tree_idx].wind_draws = &tree.instanced_wind_draws; - vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); - m_trees[tree_idx].tod_cache = swizzle_time_of_day(tree.colors); - glBindBuffer(GL_ARRAY_BUFFER, m_trees[tree_idx].vertex_buffer); - glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), nullptr, - GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glBufferSubData(GL_ARRAY_BUFFER, 0, verts * sizeof(tfrag3::PreloadedVertex), - tree.vertices.data()); - - glVertexAttribPointer(0, // location 0 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) - ); - - glVertexAttribPointer(1, // location 1 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) - ); - - glVertexAttribIPointer(2, // location 2 in the shader - 1, // 1 values per vert - GL_UNSIGNED_SHORT, // u16 - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) - ); - - glGenBuffers(1, &m_trees[tree_idx].index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].index_buffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, GL_STREAM_DRAW); - m_trees[tree_idx].index_list.resize(idx_buffer_len); - - if (wind_idx_buffer_len > 0) { - m_trees[tree_idx].wind_matrix_cache.resize(tree.instance_info.size()); - m_trees[tree_idx].has_wind = true; - glGenBuffers(1, &m_trees[tree_idx].wind_vertex_index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trees[tree_idx].wind_vertex_index_buffer); - std::vector temp; - temp.resize(wind_idx_buffer_len); - u32 off = 0; - for (auto& draw : tree.instanced_wind_draws) { - m_trees[tree_idx].wind_vertex_index_offsets.push_back(off); - 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); - } - - glActiveTexture(GL_TEXTURE10); - glGenTextures(1, &m_trees[tree_idx].time_of_day_texture); - glBindTexture(GL_TEXTURE_1D, m_trees[tree_idx].time_of_day_texture); - // just fill with zeros. this lets use use the faster texsubimage later - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, - GL_UNSIGNED_INT_8_8_8_8, m_color_result.data()); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glBindVertexArray(0); + m_has_level = false; + if (!m_load_state.loading) { + m_load_state.loading = true; + m_load_state.state = State::FIRST; } - - fmt::print("TIE temporary vis output size: {}\n", vis_temp_len); - m_cache.vis_temp.resize(vis_temp_len); - fmt::print("TIE max draws/tree: {}\n", max_draw); - m_cache.draw_idx_temp.resize(max_draw); - fmt::print("TIE draw with the most verts: {}\n", max_idx_per_draw); - - // todo share textures - fmt::print("level has {} textures\n", lev_data->textures.size()); - for (auto& tex : lev_data->textures) { - 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); - m_textures.push_back(gl_tex); + if (update_load(lev_data)) { + m_has_level = true; + m_level_name = level; + m_load_state.loading = false; } - - fmt::print("level max time of day: {}\n", time_of_day_count); - assert(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); - - fmt::print("wind: {}\n", max_wind_idx); - m_wind_vectors.resize(4 * max_wind_idx + 4); // 4x u32's per wind. - - m_level_name = level; - fmt::print("TIE setup: {:.3f}\n", tie_setup_timer.getSeconds()); + } else { + m_has_level = true; } + + if (tfrag3_setup_timer.getMs() > 5) { + fmt::print("TIE setup: {:.1f}ms s {}\n", tfrag3_setup_timer.getMs(), init_load_state); + } + + return m_has_level; } void vector_min_in_place(math::Vector4f& v, float val) { @@ -315,7 +374,7 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi } if (m_override_level && m_pending_user_level) { - setup_for_level(*m_pending_user_level, render_state); + m_has_level = setup_for_level(*m_pending_user_level, render_state); m_pending_user_level = {}; } @@ -383,7 +442,7 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi } } if (!m_override_level) { - setup_for_level(m_pc_port_data.level_name, render_state); + m_has_level = setup_for_level(m_pc_port_data.level_name, render_state); } render_all_trees(settings, render_state, prof); // todo render all... @@ -394,7 +453,7 @@ void Tie3::render_all_trees(const TfragRenderSettings& settings, ScopedProfilerNode& prof) { Timer all_tree_timer; if (m_override_level && m_pending_user_level) { - setup_for_level(*m_pending_user_level, render_state); + m_has_level = setup_for_level(*m_pending_user_level, render_state); m_pending_user_level = {}; } for (u32 i = 0; i < m_trees.size(); i++) { @@ -525,6 +584,9 @@ void Tie3::render_tree(int idx, tree.perf.verts = 0; tree.perf.full_draws = 0; tree.perf.wind_draws = 0; + if (!m_has_level) { + return; + } if (m_color_result.size() < tree.colors->size()) { m_color_result.resize(tree.colors->size()); diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.h b/game/graphics/opengl_renderer/tfrag/Tie3.h index c29863dc9c..f28b25e666 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.h +++ b/game/graphics/opengl_renderer/tfrag/Tie3.h @@ -21,7 +21,7 @@ class Tie3 : public BucketRenderer { const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); - void setup_for_level(const std::string& str, SharedRenderState* render_state); + bool setup_for_level(const std::string& str, SharedRenderState* render_state); struct WindWork { u32 paused; @@ -35,6 +35,7 @@ class Tie3 : public BucketRenderer { } m_wind_data; private: + bool update_load(const tfrag3::Level* lev_data); void discard_tree_cache(); void render_tree_wind(int idx, const TfragRenderSettings& settings, @@ -88,6 +89,7 @@ class Tie3 : public BucketRenderer { static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192; + bool m_has_level = false; char m_user_level[255] = "vi1"; std::optional m_pending_user_level = std::nullopt; bool m_override_level = false; @@ -104,4 +106,19 @@ class Tie3 : public BucketRenderer { float m_wind_multiplier = 1.f; static_assert(sizeof(WindWork) == 84 * 16); + + enum State : u32 { + FIRST = 0, + DISCARD_TREE = 0, + INIT_NEW_TREES = 1, + UPLOAD_VERTS = 2, + INIT_TEX = 4, + }; + + struct { + bool loading = false; + State state; + u32 tex = 0; + u32 vert = 0; + } m_load_state; }; diff --git a/game/graphics/texture/TexturePool.cpp b/game/graphics/texture/TexturePool.cpp index eb5133c9c5..390f01ade0 100644 --- a/game/graphics/texture/TexturePool.cpp +++ b/game/graphics/texture/TexturePool.cpp @@ -159,7 +159,7 @@ std::vector> TexturePool::convert_textures(const // the sizes given aren't the actual sizes in memory, so if you just use that, you get the // wrong answer. I solved this in the decompiler by using the size of the actual data, but we // don't really have that here. - u32 size = ((sizes[0] + sizes[1] + sizes[2] + 255) / 256) * 256; + u32 size = ((sizes[0] + sizes[1] + sizes[2] + 2047) / 256) * 256; m_tex_converter.upload(memory_base + texture_page.segment[0].block_data_ptr, texture_page.segment[0].dest, size); diff --git a/goal_src/engine/draw/drawable.gc b/goal_src/engine/draw/drawable.gc index 9481a594b2..9750396558 100644 --- a/goal_src/engine/draw/drawable.gc +++ b/goal_src/engine/draw/drawable.gc @@ -158,16 +158,18 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; tfrag - - (when (logtest? *texture-enable-user* 1) - (dotimes (gp-1 (-> *level* length)) - (let ((a1-2 (-> *level* level gp-1))) - (if (= (-> a1-2 status) 'active) - (add-tex-to-dma! *texture-pool* a1-2 0) - ) - ) - ) - ) + ;; note: PC port uses preconverted tfrag textures. + (#unless PC_PORT + (when (logtest? *texture-enable-user* 1) + (dotimes (gp-1 (-> *level* length)) + (let ((a1-2 (-> *level* level gp-1))) + (if (= (-> a1-2 status) 'active) + (add-tex-to-dma! *texture-pool* a1-2 0) + ) + ) + ) + ) + ) ;; pris