diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index 96ce2a3f69..562b472c01 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -111,22 +111,26 @@ void Level::serialize(Serializer& ser) { tex.serialize(ser); } - if (ser.is_saving()) { - ser.save(tfrag_trees.size()); - } else { - tfrag_trees.resize(ser.load()); - } - for (auto& tree : tfrag_trees) { - tree.serialize(ser); + for (int geom = 0; geom < 3; ++geom) { + if (ser.is_saving()) { + ser.save(tfrag_trees[geom].size()); + } else { + tfrag_trees[geom].resize(ser.load()); + } + for (auto& tree : tfrag_trees[geom]) { + tree.serialize(ser); + } } - if (ser.is_saving()) { - ser.save(tie_trees.size()); - } else { - tie_trees.resize(ser.load()); - } - for (auto& tree : tie_trees) { - tree.serialize(ser); + for (int geom = 0; geom < 4; ++geom) { + if (ser.is_saving()) { + ser.save(tie_trees[geom].size()); + } else { + tie_trees[geom].resize(ser.load()); + } + for (auto& tree : tie_trees[geom]) { + tree.serialize(ser); + } } ser.from_ptr(&version2); diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 5e4c5847dc..bd76a1f948 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -11,7 +11,7 @@ namespace tfrag3 { -constexpr int TFRAG3_VERSION = 9; +constexpr int TFRAG3_VERSION = 10; // These vertices should be uploaded to the GPU at load time and don't change struct PreloadedVertex { @@ -161,8 +161,8 @@ struct Level { u16 version = TFRAG3_VERSION; std::string level_name; std::vector textures; - std::vector tfrag_trees; - std::vector tie_trees; + std::array, 3> tfrag_trees; + std::array, 4> tie_trees; u16 version2 = TFRAG3_VERSION; void serialize(Serializer& ser); }; diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp index c9067de7c8..7b392e56b0 100644 --- a/common/util/FileUtil.cpp +++ b/common/util/FileUtil.cpp @@ -100,11 +100,8 @@ void write_binary_file(const std::string& name, const void* data, size_t size) { fclose(fp); } -void write_rgba_png(const std::string& name, void* data, int w, int h, bool compress) { +void write_rgba_png(const std::string& name, void* data, int w, int h) { auto flags = 0; - if (!compress) { - flags = fpng::FPNG_FORCE_UNCOMPRESSED; - } auto ok = fpng::fpng_encode_image_to_file(name.c_str(), data, w, h, 4, flags); diff --git a/common/util/FileUtil.h b/common/util/FileUtil.h index 6395e368fd..b34b73ba5c 100644 --- a/common/util/FileUtil.h +++ b/common/util/FileUtil.h @@ -18,7 +18,7 @@ std::string get_project_path(); std::string get_file_path(const std::vector& input); bool create_dir_if_needed(const std::string& path); void write_binary_file(const std::string& name, const void* data, size_t size); -void write_rgba_png(const std::string& name, void* data, int w, int h, bool compress); +void write_rgba_png(const std::string& name, void* data, int w, int h); void write_text_file(const std::string& file_name, const std::string& text); std::vector read_binary_file(const std::string& filename); std::string read_text_file(const std::string& path); diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index c463603f7d..78b0f562fd 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -19918,7 +19918,7 @@ (define-extern clone-anim-once (function handle int symbol string none :behavior process-drawable)) (define-extern convert-to-hud-object (function process-drawable hud none :behavior hud)) (define-extern clone-anim (function handle int symbol string none :behavior process-drawable)) -(define-extern merc-eye-anim (function manipy none)) +(define-extern merc-eye-anim (function process-drawable none)) (define-extern ja-anim-done? (function process symbol)) (define-extern command-get-camera (function object state state)) (define-extern camera-anim (function symbol basic entity (pointer process) :behavior camera-tracker)) ;; unused diff --git a/decompiler/data/tpage.cpp b/decompiler/data/tpage.cpp index 835051c03f..ea749c2a3d 100644 --- a/decompiler/data/tpage.cpp +++ b/decompiler/data/tpage.cpp @@ -521,7 +521,7 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) { fmt::format(file_util::get_file_path( {"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}), data.name_in_dgo, tex.name, tex.w, tex.h), - out.data(), tex.w, tex.h, false); + out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, texture_page.name); stats.successful_textures++; @@ -569,7 +569,7 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) { fmt::format(file_util::get_file_path( {"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}), data.name_in_dgo, tex.name, tex.w, tex.h), - out.data(), tex.w, tex.h, false); + out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, texture_page.name); stats.successful_textures++; @@ -599,7 +599,7 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) { fmt::format(file_util::get_file_path( {"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}), data.name_in_dgo, tex.name, tex.w, tex.h), - out.data(), tex.w, tex.h, false); + out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, texture_page.name); stats.successful_textures++; @@ -645,7 +645,7 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) { fmt::format(file_util::get_file_path( {"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}), data.name_in_dgo, tex.name, tex.w, tex.h), - out.data(), tex.w, tex.h, false); + out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, texture_page.name); stats.successful_textures++; @@ -691,7 +691,7 @@ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db) { fmt::format(file_util::get_file_path( {"assets", "textures", texture_page.name, "{}-{}-{}-{}.png"}), data.name_in_dgo, tex.name, tex.w, tex.h), - out.data(), tex.w, tex.h, false); + out.data(), tex.w, tex.h); texture_db.add_texture(texture_page.id, tex_id, out, tex.w, tex.h, tex.name, texture_page.name); stats.successful_textures++; diff --git a/decompiler/level_extractor/extract_tfrag.cpp b/decompiler/level_extractor/extract_tfrag.cpp index 30a4ea751b..5de0b508fd 100644 --- a/decompiler/level_extractor/extract_tfrag.cpp +++ b/decompiler/level_extractor/extract_tfrag.cpp @@ -8,6 +8,9 @@ namespace decompiler { namespace { + +static constexpr int GEOM_MAX = 3; + /*! * Get the index of the first draw node in an array. Works for node or tfrag. */ @@ -536,7 +539,8 @@ struct TFragColorUnpack { } }; -void emulate_dma_building_for_tfrag(const level_tools::TFragment& frag, +void emulate_dma_building_for_tfrag(int geom, + const level_tools::TFragment& frag, std::vector& vu_mem, TFragColorUnpack& color_indices, TFragExtractStats* stats) { @@ -546,13 +550,13 @@ void emulate_dma_building_for_tfrag(const level_tools::TFragment& frag, state.cl = 4; // do the "canned" unpacks - if (frag.num_level0_colors == 0) { + if (frag.num_level0_colors == 0 || geom == 2) { // we're using base - ASSERT(frag.num_level1_colors == 0); + // ASSERT(frag.num_level1_colors == 0); stats->num_base++; emulate_chain(state, frag.dma_qwc[1] * 4, (const u32*)frag.dma_base.data(), vu_mem.data()); - } else if (frag.num_level1_colors == 0) { + } else if (frag.num_level1_colors == 0 || geom == 1) { stats->num_l0++; emulate_chain(state, frag.dma_qwc[3] * 4, (const u32*)frag.dma_common_and_level0.data(), vu_mem.data()); @@ -2070,7 +2074,8 @@ void make_tfrag3_data(std::map>& draws, } } -void emulate_tfrags(const std::vector& frags, +void emulate_tfrags(int geom, + const std::vector& frags, const std::string& debug_name, const std::vector& map, tfrag3::Level& level_out, @@ -2087,7 +2092,7 @@ void emulate_tfrags(const std::vector& frags, for (auto& frag : frags) { TFragColorUnpack color_indices; - emulate_dma_building_for_tfrag(frag, vu_mem, color_indices, &stats); + emulate_dma_building_for_tfrag(geom, frag, vu_mem, color_indices, &stats); VuMemWrapper mem(vu_mem); auto draws = emulate_tfrag_execution(frag, mem, color_indices, &stats); all_draws.insert(all_draws.end(), draws.begin(), draws.end()); @@ -2137,75 +2142,78 @@ void extract_tfrag(const level_tools::DrawableTreeTfrag* tree, const std::vector>& expected_missing_textures, tfrag3::Level& out, bool dump_level) { - tfrag3::TfragTree this_tree; - if (tree->my_type() == "drawable-tree-tfrag") { - this_tree.kind = tfrag3::TFragmentTreeKind::NORMAL; - } else if (tree->my_type() == "drawable-tree-dirt-tfrag") { - this_tree.kind = tfrag3::TFragmentTreeKind::DIRT; - } else if (tree->my_type() == "drawable-tree-ice-tfrag") { - this_tree.kind = tfrag3::TFragmentTreeKind::ICE; - } else if (tree->my_type() == "drawable-tree-lowres-tfrag") { - this_tree.kind = tfrag3::TFragmentTreeKind::LOWRES; - } else if (tree->my_type() == "drawable-tree-trans-tfrag") { - this_tree.kind = tfrag3::TFragmentTreeKind::TRANS; - } else { - fmt::print("unknown tfrag tree kind: {}\n", tree->my_type()); - ASSERT(false); - } + // go through 4 lods(?) + for (int geom = 0; geom < GEOM_MAX; ++geom) { + tfrag3::TfragTree this_tree; + if (tree->my_type() == "drawable-tree-tfrag") { + this_tree.kind = tfrag3::TFragmentTreeKind::NORMAL; + } else if (tree->my_type() == "drawable-tree-dirt-tfrag") { + this_tree.kind = tfrag3::TFragmentTreeKind::DIRT; + } else if (tree->my_type() == "drawable-tree-ice-tfrag") { + this_tree.kind = tfrag3::TFragmentTreeKind::ICE; + } else if (tree->my_type() == "drawable-tree-lowres-tfrag") { + this_tree.kind = tfrag3::TFragmentTreeKind::LOWRES; + } else if (tree->my_type() == "drawable-tree-trans-tfrag") { + this_tree.kind = tfrag3::TFragmentTreeKind::TRANS; + } else { + fmt::print("unknown tfrag tree kind: {}\n", tree->my_type()); + ASSERT(false); + } - ASSERT(tree->length == (int)tree->arrays.size()); - ASSERT(tree->length > 0); + ASSERT(tree->length == (int)tree->arrays.size()); + ASSERT(tree->length > 0); - auto last_array = tree->arrays.back().get(); + auto last_array = tree->arrays.back().get(); - auto as_tfrag_array = dynamic_cast(last_array); - ASSERT(as_tfrag_array); - ASSERT(as_tfrag_array->length == (int)as_tfrag_array->tfragments.size()); - ASSERT(as_tfrag_array->length > 0); - u16 idx = as_tfrag_array->tfragments.front().id; - for (auto& elt : as_tfrag_array->tfragments) { - ASSERT(elt.id == idx); - idx++; - } - bool ok = verify_node_indices(tree); - ASSERT(ok); - fmt::print(" tree has {} arrays and {} tfragments\n", tree->length, as_tfrag_array->length); + auto as_tfrag_array = dynamic_cast(last_array); + ASSERT(as_tfrag_array); + ASSERT(as_tfrag_array->length == (int)as_tfrag_array->tfragments.size()); + ASSERT(as_tfrag_array->length > 0); + u16 idx = as_tfrag_array->tfragments.front().id; + for (auto& elt : as_tfrag_array->tfragments) { + ASSERT(elt.id == idx); + idx++; + } + bool ok = verify_node_indices(tree); + ASSERT(ok); + fmt::print(" tree has {} arrays and {} tfragments\n", tree->length, as_tfrag_array->length); - auto vis_nodes = extract_vis_data(tree, as_tfrag_array->tfragments.front().id); - this_tree.bvh.first_leaf_node = vis_nodes.first_child_node; - this_tree.bvh.last_leaf_node = vis_nodes.last_child_node; - this_tree.bvh.num_roots = vis_nodes.num_roots; - this_tree.bvh.only_children = vis_nodes.only_children; - this_tree.bvh.first_root = vis_nodes.first_root; - this_tree.bvh.vis_nodes = std::move(vis_nodes.vis_nodes); + auto vis_nodes = extract_vis_data(tree, as_tfrag_array->tfragments.front().id); + this_tree.bvh.first_leaf_node = vis_nodes.first_child_node; + this_tree.bvh.last_leaf_node = vis_nodes.last_child_node; + this_tree.bvh.num_roots = vis_nodes.num_roots; + this_tree.bvh.only_children = vis_nodes.only_children; + this_tree.bvh.first_root = vis_nodes.first_root; + this_tree.bvh.vis_nodes = std::move(vis_nodes.vis_nodes); - std::unordered_map tfrag_parents; - // for (auto& node : this_tree.vis_nodes) { - for (size_t node_idx = 0; node_idx < this_tree.bvh.vis_nodes.size(); node_idx++) { - const auto& node = this_tree.bvh.vis_nodes[node_idx]; - if (node.flags == 0) { - for (int i = 0; i < node.num_kids; i++) { - tfrag_parents[node.child_id + i] = node_idx; + std::unordered_map tfrag_parents; + // for (auto& node : this_tree.vis_nodes) { + for (size_t node_idx = 0; node_idx < this_tree.bvh.vis_nodes.size(); node_idx++) { + const auto& node = this_tree.bvh.vis_nodes[node_idx]; + if (node.flags == 0) { + for (int i = 0; i < node.num_kids; i++) { + tfrag_parents[node.child_id + i] = node_idx; + } } } - } - // ASSERT(result.vis_nodes.last_child_node + 1 == idx); + // ASSERT(result.vis_nodes.last_child_node + 1 == idx); - emulate_tfrags(as_tfrag_array->tfragments, debug_name, map, out, this_tree, tex_db, - expected_missing_textures, dump_level); - extract_time_of_day(tree, this_tree); + emulate_tfrags(geom, as_tfrag_array->tfragments, debug_name, map, out, this_tree, tex_db, + expected_missing_textures, dump_level); + extract_time_of_day(tree, this_tree); - for (auto& draw : this_tree.draws) { - for (auto& str : draw.vis_groups) { - auto it = tfrag_parents.find(str.vis_idx_in_pc_bvh); - if (it == tfrag_parents.end()) { - str.vis_idx_in_pc_bvh = UINT32_MAX; - } else { - str.vis_idx_in_pc_bvh = it->second; + for (auto& draw : this_tree.draws) { + for (auto& str : draw.vis_groups) { + auto it = tfrag_parents.find(str.vis_idx_in_pc_bvh); + if (it == tfrag_parents.end()) { + str.vis_idx_in_pc_bvh = UINT32_MAX; + } else { + str.vis_idx_in_pc_bvh = it->second; + } } + merge_groups(draw.vis_groups); } - merge_groups(draw.vis_groups); + out.tfrag_trees[geom].push_back(this_tree); } - out.tfrag_trees.push_back(this_tree); } } // namespace decompiler diff --git a/decompiler/level_extractor/extract_tie.cpp b/decompiler/level_extractor/extract_tie.cpp index 95a5e3016e..52ddbb3135 100644 --- a/decompiler/level_extractor/extract_tie.cpp +++ b/decompiler/level_extractor/extract_tie.cpp @@ -161,6 +161,8 @@ void extract_vis_data(const level_tools::DrawableTreeInstanceTie* tree, } } +constexpr int GEOM_MAX = 4; // the amount of geoms + // Each TIE prototype is broken up into "fragments". These "fragments" have some maximum size based // on the VU memory limit, so an instance may have multiple fragments, depending on how many // vertices are in the model. @@ -362,7 +364,7 @@ std::array extract_tie_matrix(const u16* data) { } // geometry we use (todo, should really look at this) -constexpr int GEOM_IDX = 1; // todo 0 or 1?? +constexpr int GEOM_IDX = 0; // todo 0 or 1?? /*! * Confirm that the initial value of all wind vectors is 0. @@ -388,7 +390,8 @@ void check_wind_vectors_zero(const std::vector& protos, Ref wind_r // get per-instance info from the level data std::vector collect_instance_info( const level_tools::DrawableInlineArrayInstanceTie* instances, - const std::vector* protos) { + const std::vector* protos, + int geo) { std::vector result; // loop over instances in level @@ -418,13 +421,13 @@ std::vector collect_instance_info( // and this is only the indices.... there's yet another lookup on the VU auto& proto = protos->at(info.prototype_idx); - u32 offset_bytes = proto.base_qw[GEOM_IDX] * 16; + u32 offset_bytes = proto.base_qw[geo] * 16; // loop over frags. this is only the per-instance info so only colors indices. We know the // location/layout of the color data from the EE asm code. - for (int frag_idx = 0; frag_idx < proto.frag_count[GEOM_IDX]; frag_idx++) { + for (int frag_idx = 0; frag_idx < proto.frag_count[geo]; frag_idx++) { TieInstanceFragInfo frag_info; // read the number of quadwords - u32 num_color_qwc = proto.color_index_qwc.at(proto.index_start[GEOM_IDX] + frag_idx); + u32 num_color_qwc = proto.color_index_qwc.at(proto.index_start[geo] + frag_idx); // loop over 4-byte words for (u32 i = 0; i < num_color_qwc * 4; i++) { // loop over bytes in word @@ -472,7 +475,8 @@ u32 remap_texture(u32 original, const std::vector& ma void update_proto_info(std::vector* out, const std::vector& map, const TextureDB& tdb, - const std::vector& protos) { + const std::vector& protos, + int geo) { out->resize(std::max(out->size(), protos.size())); for (size_t i = 0; i < protos.size(); i++) { const auto& proto = protos[i]; @@ -497,12 +501,12 @@ void update_proto_info(std::vector* out, } // loop over fragments in the proto. This is the actual mesh data data and drawing settings - for (int frag_idx = 0; frag_idx < proto.frag_count[GEOM_IDX]; frag_idx++) { + for (int frag_idx = 0; frag_idx < proto.frag_count[geo]; frag_idx++) { TieFrag frag_info; // loop over adgif shaders - for (int tex_idx = 0; - tex_idx < proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).tex_count / 5; tex_idx++) { + for (int tex_idx = 0; tex_idx < proto.geometry[geo].tie_fragments.at(frag_idx).tex_count / 5; + tex_idx++) { // this adgif shader data is modified in the real game by the login methods. // all TIE things have pretty normal adgif shaders @@ -510,7 +514,7 @@ void update_proto_info(std::vector* out, AdgifInfo adgif; // pointer to the level data - auto& gif_data = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].gif_data; + auto& gif_data = proto.geometry[geo].tie_fragments[frag_idx].gif_data; // address for the first adgif shader qw. u8 ra_tex0 = gif_data.at(16 * (tex_idx * 5 + 0) + 8); @@ -582,20 +586,20 @@ void update_proto_info(std::vector* out, } // they store a vertex count. we later use this to sanity check out mesh extraction - frag_info.expected_dverts = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].num_dverts; + frag_info.expected_dverts = proto.geometry[geo].tie_fragments[frag_idx].num_dverts; // each frag also has "other" data. This is some index data that the VU program uses. // it comes in gif_data, after tex_qwc (determined from EE program) - int tex_qwc = proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).tex_count; - int other_qwc = proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).gif_count; + int tex_qwc = proto.geometry[geo].tie_fragments.at(frag_idx).tex_count; + int other_qwc = proto.geometry[geo].tie_fragments.at(frag_idx).gif_count; frag_info.other_gif_data.resize(16 * other_qwc); memcpy(frag_info.other_gif_data.data(), - proto.geometry[GEOM_IDX].tie_fragments[frag_idx].gif_data.data() + (16 * tex_qwc), + proto.geometry[geo].tie_fragments[frag_idx].gif_data.data() + (16 * tex_qwc), 16 * other_qwc); // each frag's "point" data. These are stored as int16's, but get unpacked to 32-bit ints by // the VIF. (determined from EE program) - const auto& pr = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].point_ref; + const auto& pr = proto.geometry[geo].tie_fragments[frag_idx].point_ref; int in_qw = pr.size() / 16; int out_qw = in_qw * 2; frag_info.points_data.resize(out_qw * 16); @@ -2263,98 +2267,103 @@ void extract_tie(const level_tools::DrawableTreeInstanceTie* tree, const TextureDB& tex_db, tfrag3::Level& out, bool dump_level) { - tfrag3::TieTree this_tree; + for (int geo = 0; geo < GEOM_MAX; ++geo) { + tfrag3::TieTree this_tree; - // sanity check the vis tree (not a perfect check, but this is used in game and should be right) - ASSERT(tree->length == (int)tree->arrays.size()); - ASSERT(tree->length > 0); - auto last_array = tree->arrays.back().get(); - auto as_instance_array = dynamic_cast(last_array); - ASSERT(as_instance_array); - ASSERT(as_instance_array->length == (int)as_instance_array->instances.size()); - ASSERT(as_instance_array->length > 0); - u16 idx = as_instance_array->instances.front().id; - for (auto& elt : as_instance_array->instances) { - ASSERT(elt.id == idx); - idx++; - } - bool ok = verify_node_indices(tree); - ASSERT(ok); - fmt::print(" tree has {} arrays and {} instances\n", tree->length, as_instance_array->length); - - // extract the vis tree. Note that this extracts the tree only down to the last draw node, a - // parent of between 1 and 8 instances. - extract_vis_data(tree, as_instance_array->instances.front().id, this_tree); - - // we use the index of the instance in the instance list as its index. But this is different - // from its visibility index. This map goes from instance index to the parent node in the vis - // tree. later, we can use this to remap from instance idx to the visiblity node index. - std::unordered_map instance_parents; - for (size_t node_idx = 0; node_idx < this_tree.bvh.vis_nodes.size(); node_idx++) { - const auto& node = this_tree.bvh.vis_nodes[node_idx]; - if (node.flags == 0) { - for (int i = 0; i < node.num_kids; i++) { - instance_parents[node.child_id + i] = node_idx; - } + // sanity check the vis tree (not a perfect check, but this is used in game and should be right) + ASSERT(tree->length == (int)tree->arrays.size()); + ASSERT(tree->length > 0); + auto last_array = tree->arrays.back().get(); + auto as_instance_array = dynamic_cast(last_array); + ASSERT(as_instance_array); + ASSERT(as_instance_array->length == (int)as_instance_array->instances.size()); + ASSERT(as_instance_array->length > 0); + u16 idx = as_instance_array->instances.front().id; + for (auto& elt : as_instance_array->instances) { + ASSERT(elt.id == idx); + idx++; } - } + bool ok = verify_node_indices(tree); + ASSERT(ok); + fmt::print(" tree has {} arrays and {} instances\n", tree->length, + as_instance_array->length); - // convert level format data to a nicer format - auto info = collect_instance_info(as_instance_array, &tree->prototypes.prototype_array_tie.data); - update_proto_info(&info, tex_map, tex_db, tree->prototypes.prototype_array_tie.data); - check_wind_vectors_zero(info, tree->prototypes.wind_vectors); - // determine draws from VU program - emulate_tie_prototype_program(info); - emulate_tie_instance_program(info); - emulate_kicks(info); + // extract the vis tree. Note that this extracts the tree only down to the last draw node, a + // parent of between 1 and 8 instances. + extract_vis_data(tree, as_instance_array->instances.front().id, this_tree); - // debug save to .obj - if (dump_level) { - auto dir = file_util::get_file_path({fmt::format("debug_out/tie-{}/", debug_name)}); - file_util::create_dir_if_needed(dir); - for (auto& proto : info) { - auto data = debug_dump_proto_to_obj(proto); - file_util::write_text_file(fmt::format("{}/{}.obj", dir, proto.name), data); - } - - auto full = dump_full_to_obj(info); - file_util::write_text_file(fmt::format("{}/ALL.obj", dir), full); - } - - // create time of day data. - auto full_palette = make_big_palette(info); - - // create draws - add_vertices_and_static_draw(this_tree, out, tex_db, info); - - // remap vis indices and merge - for (auto& draw : this_tree.static_draws) { - for (auto& str : draw.vis_groups) { - auto it = instance_parents.find(str.vis_idx_in_pc_bvh); - if (it == instance_parents.end()) { - str.vis_idx_in_pc_bvh = UINT32_MAX; - } else { - str.vis_idx_in_pc_bvh = it->second; - } - } - merge_groups(draw.vis_groups); - } - - for (auto& draw : this_tree.instanced_wind_draws) { - for (auto& str : draw.instance_groups) { - auto it = instance_parents.find(str.vis_idx); - if (it == instance_parents.end()) { - str.vis_idx = UINT32_MAX; - } else { - str.vis_idx = it->second; + // we use the index of the instance in the instance list as its index. But this is different + // from its visibility index. This map goes from instance index to the parent node in the vis + // tree. later, we can use this to remap from instance idx to the visiblity node index. + std::unordered_map instance_parents; + for (size_t node_idx = 0; node_idx < this_tree.bvh.vis_nodes.size(); node_idx++) { + const auto& node = this_tree.bvh.vis_nodes[node_idx]; + if (node.flags == 0) { + for (int i = 0; i < node.num_kids; i++) { + instance_parents[node.child_id + i] = node_idx; + } } } - merge_groups(draw.instance_groups); - } + // convert level format data to a nicer format + auto info = + collect_instance_info(as_instance_array, &tree->prototypes.prototype_array_tie.data, geo); + update_proto_info(&info, tex_map, tex_db, tree->prototypes.prototype_array_tie.data, geo); + check_wind_vectors_zero(info, tree->prototypes.wind_vectors); + // determine draws from VU program + emulate_tie_prototype_program(info); + emulate_tie_instance_program(info); + emulate_kicks(info); - this_tree.colors = full_palette.colors; - fmt::print("TIE tree has {} draws\n", this_tree.static_draws.size()); - out.tie_trees.push_back(std::move(this_tree)); + // debug save to .obj + if (dump_level) { + auto dir = + file_util::get_file_path({fmt::format("debug_out/lod{}-tie-{}/", geo, debug_name)}); + file_util::create_dir_if_needed(dir); + for (auto& proto : info) { + auto data = debug_dump_proto_to_obj(proto); + file_util::write_text_file(fmt::format("{}/{}.obj", dir, proto.name), data); + } + + auto full = dump_full_to_obj(info); + file_util::write_text_file(fmt::format("{}/ALL.obj", dir), full); + } + + // create time of day data. + auto full_palette = make_big_palette(info); + + // create draws + add_vertices_and_static_draw(this_tree, out, tex_db, info); + + // remap vis indices and merge + for (auto& draw : this_tree.static_draws) { + for (auto& str : draw.vis_groups) { + auto it = instance_parents.find(str.vis_idx_in_pc_bvh); + if (it == instance_parents.end()) { + str.vis_idx_in_pc_bvh = UINT32_MAX; + } else { + str.vis_idx_in_pc_bvh = it->second; + } + } + merge_groups(draw.vis_groups); + } + + for (auto& draw : this_tree.instanced_wind_draws) { + for (auto& str : draw.instance_groups) { + auto it = instance_parents.find(str.vis_idx); + if (it == instance_parents.end()) { + str.vis_idx = UINT32_MAX; + } else { + str.vis_idx = it->second; + } + } + + merge_groups(draw.instance_groups); + } + + this_tree.colors = full_palette.colors; + fmt::print("TIE tree {} has {} draws\n", geo, this_tree.static_draws.size()); + out.tie_trees[geo].push_back(std::move(this_tree)); + } } } // namespace decompiler diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index 95eeed3515..dce38b1033 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -256,4 +256,18 @@ int PadAnalogValue(Pad::Analog analog, int port) { return Pad::AnalogValue(g_settings.pad_mapping_info, analog, port); } +void SetLod(RendererTreeType tree, int lod) { + switch (tree) { + case RendererTreeType::TFRAG3: + g_global_settings.lod_tfrag = lod; + break; + case RendererTreeType::TIE3: + g_global_settings.lod_tie = lod; + break; + default: + lg::error("Invalid tree {} specified for SetLod ({})", tree, lod); + break; + } +} + } // namespace Gfx diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index 2a947e4b09..a11dcb4c31 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -71,6 +71,10 @@ struct GfxGlobalSettings { // current renderer const GfxRendererModule* renderer; + + // lod settings, used by bucket renderers + int lod_tfrag = 0; + int lod_tie = 0; }; namespace Gfx { @@ -105,4 +109,8 @@ s64 get_mapped_button(s64 pad, s64 button); int PadIsPressed(Pad::Button button, int port); int PadAnalogValue(Pad::Analog analog, int port); +// matching enum in kernel-defs.gc !! +enum class RendererTreeType { NONE = 0, TFRAG3 = 1, TIE3 = 2, INVALID }; +void SetLod(RendererTreeType tree, int lod); + } // namespace Gfx diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index d57b1be557..bc26c6f8eb 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -51,9 +51,9 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool) glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(opengl_error_callback, nullptr); // disable specific errors - // const GLuint gl_error_ignores_api_perf[1] = {}; - // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE, 0, - // &gl_error_ignores_api_perf[0], GL_FALSE); + const GLuint gl_error_ignores_api_other[1] = {0x20071}; + glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 1, + &gl_error_ignores_api_other[0], GL_FALSE); lg::debug("OpenGL context information: {}", (const char*)glGetString(GL_VERSION)); @@ -256,8 +256,7 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { if (settings.save_screenshot) { finish_screenshot(settings.screenshot_path, settings.window_width_px, settings.window_height_px, - settings.lbox_width_px, settings.lbox_height_px, - settings.screenshot_should_compress); + settings.lbox_width_px, settings.lbox_height_px); } m_render_state.loader.update(); @@ -431,8 +430,7 @@ void OpenGLRenderer::finish_screenshot(const std::string& output_name, int width, int height, int x, - int y, - bool compress) { + int y) { std::vector buffer(width * height); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadBuffer(GL_BACK); @@ -448,5 +446,5 @@ void OpenGLRenderer::finish_screenshot(const std::string& output_name, for (auto& px : buffer) { px |= 0xff000000; } - file_util::write_rgba_png(output_name, buffer.data(), width, height, compress); + file_util::write_rgba_png(output_name, buffer.data(), width, height); } diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index c26b22b5b2..111ef0397f 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -18,7 +18,6 @@ struct RenderOptions { bool playing_from_dump = false; bool save_screenshot = false; - bool screenshot_should_compress = false; std::string screenshot_path; }; @@ -35,12 +34,7 @@ class OpenGLRenderer { void init_bucket_renderers(); void draw_renderer_selection_window(); - void finish_screenshot(const std::string& output_name, - int px, - int py, - int x, - int y, - bool compress); + void finish_screenshot(const std::string& output_name, int px, int py, int x, int y); template void init_bucket_renderer(const std::string& name, BucketId id, Args&&... args) { diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index 8c75e5acf0..e24341db52 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -96,9 +96,8 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { } if (ImGui::BeginMenu("Gfx Dump")) { - ImGui::Checkbox("Compress Screenshot", &m_compress_screenshot); ImGui::MenuItem("Screenshot Next Frame!", nullptr, &m_want_screenshot); - ImGui::InputText("File", m_screenshot_save_name, 30); + ImGui::InputText("File", m_screenshot_save_name, 50); ImGui::Separator(); ImGui::MenuItem("Dump Next Frame!", nullptr, &m_want_save); bool old_replay = m_want_replay; diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index ecf6ec0b0f..6bb5402e30 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -61,8 +61,6 @@ class OpenGlDebugGui { return false; } - bool screenshot_compress_flag() { return m_compress_screenshot; } - bool get_vsync_flag() { return m_vsync; } bool framelimiter = false; @@ -78,7 +76,6 @@ class OpenGlDebugGui { bool m_want_save = false; bool m_want_replay = false; bool m_want_dump_load = false; - bool m_compress_screenshot = false; bool m_want_screenshot = false; char m_dump_save_name[256] = "dump.bin"; char m_screenshot_save_name[256] = "screenshot.png"; diff --git a/game/graphics/opengl_renderer/tfrag/TFragment.cpp b/game/graphics/opengl_renderer/tfrag/TFragment.cpp index c6c4015fc4..e6062999dd 100644 --- a/game/graphics/opengl_renderer/tfrag/TFragment.cpp +++ b/game/graphics/opengl_renderer/tfrag/TFragment.cpp @@ -145,7 +145,7 @@ void TFragment::render(DmaFollower& dma, } auto t3prof = prof.make_scoped_child("t3"); - m_tfrag3.render_matching_trees(m_tree_kinds, settings, render_state, t3prof); + m_tfrag3.render_matching_trees(m_tfrag3.lod(), m_tree_kinds, settings, render_state, t3prof); } m_debug_string += fmt::format("fail: {}\n", dma.current_tag().print()); @@ -186,8 +186,9 @@ void TFragment::render(DmaFollower& dma, auto t3prof = prof.make_scoped_child(level_names[i]); m_many_level_render.tfrag_level_renderers[i]->debug_render_all_trees_nolores( - settings, render_state, t3prof); - m_many_level_render.tie_level_renderers[i]->render_all_trees(settings, render_state, + 0, settings, render_state, t3prof); + // always renders max lod + m_many_level_render.tie_level_renderers[i]->render_all_trees(0, settings, render_state, t3prof); } } diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp index d92bf0e0bc..339d61cc1a 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp @@ -46,7 +46,9 @@ bool Tfrag3::update_load(const std::vector& tree_kind m_load_state.state = State::FREE_OLD_TREES; break; case State::FREE_OLD_TREES: - m_cached_trees.clear(); + for (int geom = 0; geom < GEOM_MAX; ++geom) { + m_cached_trees[geom].clear(); + } m_load_state.state = State::INIT_NEW_TREES; break; case State::INIT_NEW_TREES: @@ -56,75 +58,77 @@ bool Tfrag3::update_load(const std::vector& tree_kind 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; + for (int geom = 0; geom < GEOM_MAX; ++geom) { + for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].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(); + const auto& tree = lev_data->tfrag_trees[geom][tree_idx]; - 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(); + auto& tree_cache = m_cached_trees[geom].emplace_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); } - 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); } } @@ -139,21 +143,23 @@ bool Tfrag3::update_load(const std::vector& tree_kind case State::UPLOAD_VERTS: { constexpr u32 MAX_VERTS = 40000; bool remaining = false; - for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees.size(); tree_idx++) { - const auto& tree = lev_data->tfrag_trees[tree_idx]; + for (int geom = 0; geom < GEOM_MAX; ++geom) { + for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) { + const auto& tree = lev_data->tfrag_trees[geom][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; + 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[geom][tree_idx].vao); + glBindBuffer(GL_ARRAY_BUFFER, m_cached_trees[geom][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; + } } } } @@ -211,13 +217,14 @@ bool Tfrag3::setup_for_level(const std::vector& tree_ return m_has_level; } -void Tfrag3::render_tree(const TfragRenderSettings& settings, +void Tfrag3::render_tree(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { if (!m_has_level) { return; } - auto& tree = m_cached_trees.at(settings.tree_idx); + auto& tree = m_cached_trees.at(geom).at(settings.tree_idx); ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID); if (m_color_result.size() < tree.colors->size()) { @@ -296,71 +303,74 @@ void Tfrag3::render_tree(const TfragRenderSettings& settings, * This is intended to be used only for debugging when we can't easily get commands for all trees * working. */ -void Tfrag3::render_all_trees(const TfragRenderSettings& settings, +void Tfrag3::render_all_trees(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { TfragRenderSettings settings_copy = settings; - for (size_t i = 0; i < m_cached_trees.size(); i++) { - if (m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::INVALID) { + for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { + if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID) { settings_copy.tree_idx = i; - render_tree(settings_copy, render_state, prof); + render_tree(geom, settings_copy, render_state, prof); } } } -void Tfrag3::render_matching_trees(const std::vector& trees, +void Tfrag3::render_matching_trees(int geom, + const std::vector& trees, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { TfragRenderSettings settings_copy = settings; - for (size_t i = 0; i < m_cached_trees.size(); i++) { - m_cached_trees[i].reset_stats(); - if (!m_cached_trees[i].allowed) { + for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { + auto& tree = m_cached_trees[geom][i]; + tree.reset_stats(); + if (!tree.allowed) { continue; } - if (std::find(trees.begin(), trees.end(), m_cached_trees[i].kind) != trees.end() || - m_cached_trees[i].forced) { - m_cached_trees[i].rendered_this_frame = true; + if (std::find(trees.begin(), trees.end(), tree.kind) != trees.end() || tree.forced) { + tree.rendered_this_frame = true; settings_copy.tree_idx = i; - render_tree(settings_copy, render_state, prof); - if (m_cached_trees[i].cull_debug) { + render_tree(geom, settings_copy, render_state, prof); + if (tree.cull_debug) { render_tree_cull_debug(settings_copy, render_state, prof); } } } } -void Tfrag3::debug_render_all_trees_nolores(const TfragRenderSettings& settings, +void Tfrag3::debug_render_all_trees_nolores(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { TfragRenderSettings settings_copy = settings; for (size_t i = 0; i < m_cached_trees.size(); i++) { - if (m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::INVALID && - m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS && - m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES) { + if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID && + m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS && + m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::LOWRES) { settings_copy.tree_idx = i; - render_tree(settings_copy, render_state, prof); + render_tree(geom, settings_copy, render_state, prof); } } - for (size_t i = 0; i < m_cached_trees.size(); i++) { - if (m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::INVALID && - m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS && - m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES) { - settings_copy.tree_idx = i; - // render_tree_cull_debug(settings_copy, render_state, prof); - } - } + // for (size_t i = 0; i < m_cached_trees.size(); i++) { + // if (m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::INVALID && + // m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES_TRANS && + // m_cached_trees[i].kind != tfrag3::TFragmentTreeKind::LOWRES) { + // settings_copy.tree_idx = i; + // render_tree_cull_debug(settings_copy, render_state, prof); + // } + // } } void Tfrag3::draw_debug_window() { for (int i = 0; i < (int)m_cached_trees.size(); i++) { - auto& tree = m_cached_trees[i]; + auto& tree = m_cached_trees[lod()][i]; if (tree.kind == tfrag3::TFragmentTreeKind::INVALID) { continue; } ImGui::PushID(i); - ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[i].kind]); + ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[lod()][i].kind]); ImGui::SameLine(); ImGui::Checkbox("Allow?", &tree.allowed); ImGui::SameLine(); @@ -376,16 +386,18 @@ void Tfrag3::draw_debug_window() { void Tfrag3::discard_tree_cache() { m_textures = nullptr; - for (auto& tree : m_cached_trees) { - if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) { - glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); - glDeleteTextures(1, &tree.time_of_day_texture); - glDeleteBuffers(1, &tree.vertex_buffer); - glDeleteBuffers(1, &tree.index_buffer); - glDeleteVertexArrays(1, &tree.vao); + for (int geom = 0; geom < GEOM_MAX; ++geom) { + for (auto& tree : m_cached_trees[geom]) { + if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) { + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + glDeleteTextures(1, &tree.time_of_day_texture); + glDeleteBuffers(1, &tree.vertex_buffer); + glDeleteBuffers(1, &tree.index_buffer); + glDeleteVertexArrays(1, &tree.vao); + } } + m_cached_trees[geom].clear(); } - m_cached_trees.clear(); } namespace { @@ -472,7 +484,7 @@ void Tfrag3::render_tree_cull_debug(const TfragRenderSettings& settings, ScopedProfilerNode& prof) { // generate debug verts: m_debug_vert_data.clear(); - auto& tree = m_cached_trees.at(settings.tree_idx); + auto& tree = m_cached_trees.at(settings.tree_idx).at(lod()); debug_vis_draw(tree.vis->first_root, tree.vis->first_root, tree.vis->num_roots, 1, tree.vis->vis_nodes, m_debug_vert_data); diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.h b/game/graphics/opengl_renderer/tfrag/Tfrag3.h index 6cec242d87..0548a36e0f 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.h +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.h @@ -2,6 +2,7 @@ #include "common/custom_data/Tfrag3Data.h" #include "common/math/Vector.h" +#include "game/graphics/gfx.h" #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/pipelines/opengl.h" #include "game/graphics/opengl_renderer/tfrag/tfrag_common.h" @@ -12,20 +13,24 @@ class Tfrag3 { Tfrag3(); ~Tfrag3(); - void debug_render_all_trees_nolores(const TfragRenderSettings& settings, + void debug_render_all_trees_nolores(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); - void render_all_trees(const TfragRenderSettings& settings, + void render_all_trees(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); - void render_matching_trees(const std::vector& trees, + void render_matching_trees(int geom, + const std::vector& trees, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); - void render_tree(const TfragRenderSettings& settings, + void render_tree(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); @@ -47,7 +52,11 @@ class Tfrag3 { bool update_load(const std::vector& tree_kinds, const tfrag3::Level* lev_data); + int lod() const { return Gfx::g_global_settings.lod_tfrag; } + private: + static constexpr int GEOM_MAX = 3; + struct TreeCache { tfrag3::TFragmentTreeKind kind; GLuint vertex_buffer = -1; @@ -82,7 +91,7 @@ class Tfrag3 { std::string m_level_name; const std::vector* m_textures = nullptr; - std::vector m_cached_trees; + std::array, GEOM_MAX> m_cached_trees; std::vector> m_color_result; @@ -92,7 +101,7 @@ class Tfrag3 { u64 m_load_id = -1; // in theory could be up to 4096, I think, but we don't see that many... - // should be easy to increase (will require a shader change too for indexing) + // should be easy to increase static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192; static constexpr int DEBUG_TRI_COUNT = 4096; diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.cpp b/game/graphics/opengl_renderer/tfrag/Tie3.cpp index ea1c2bd20d..6c6005d108 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tie3.cpp @@ -20,7 +20,9 @@ bool Tie3::update_load(const tfrag3::Level* lev_data) { m_wind_vectors.clear(); // We changed level! discard_tree_cache(); - m_trees.resize(lev_data->tie_trees.size()); + for (int geo = 0; geo < 4; ++geo) { + m_trees[geo].resize(lev_data->tie_trees[geo].size()); + } m_load_state.state = INIT_NEW_TREES; break; case INIT_NEW_TREES: { @@ -30,106 +32,110 @@ bool Tie3::update_load(const tfrag3::Level* lev_data) { 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; + // set up each tree for each lod + for (int geo = 0; geo < 4; ++geo) { + for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees[geo].size(); tree_idx++) { + size_t idx_buffer_len = 0; + size_t wind_idx_buffer_len = 0; + const auto& tree = lev_data->tie_trees[geo][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) { - 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(); + 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()); + auto& lod_tree = m_trees.at(geo); + glGenVertexArrays(1, &lod_tree[tree_idx].vao); + glBindVertexArray(lod_tree[tree_idx].vao); + glGenBuffers(1, &lod_tree[tree_idx].vertex_buffer); + lod_tree[tree_idx].vert_count = verts; + lod_tree[tree_idx].draws = &tree.static_draws; // todo - should we just copy this? + lod_tree[tree_idx].colors = &tree.colors; + lod_tree[tree_idx].vis = &tree.bvh; + lod_tree[tree_idx].instance_info = &tree.instance_info; + lod_tree[tree_idx].wind_draws = &tree.instanced_wind_draws; + vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); + lod_tree[tree_idx].tod_cache = swizzle_time_of_day(tree.colors); + glBindBuffer(GL_ARRAY_BUFFER, lod_tree[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, &lod_tree[tree_idx].index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lod_tree[tree_idx].index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_len * sizeof(u32), nullptr, + GL_STREAM_DRAW); + lod_tree[tree_idx].index_list.resize(idx_buffer_len); + + if (wind_idx_buffer_len > 0) { + lod_tree[tree_idx].wind_matrix_cache.resize(tree.instance_info.size()); + lod_tree[tree_idx].has_wind = true; + glGenBuffers(1, &lod_tree[tree_idx].wind_vertex_index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lod_tree[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) { + lod_tree[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); } - glBufferData(GL_ELEMENT_ARRAY_BUFFER, wind_idx_buffer_len * sizeof(u32), temp.data(), - GL_STATIC_DRAW); + glActiveTexture(GL_TEXTURE10); + glGenTextures(1, &lod_tree[tree_idx].time_of_day_texture); + glBindTexture(GL_TEXTURE_1D, lod_tree[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); } - - 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); @@ -149,19 +155,21 @@ bool Tie3::update_load(const tfrag3::Level* lev_data) { 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; + for (int geo = 0; geo < 4; ++geo) { + for (size_t tree_idx = 0; tree_idx < lev_data->tie_trees[geo].size(); tree_idx++) { + const auto& tree = lev_data->tie_trees[geo][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[geo][tree_idx].vao); + glBindBuffer(GL_ARRAY_BUFFER, m_trees[geo][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; + } } } } @@ -330,18 +338,20 @@ void do_wind_math(u16 wind_idx, } void Tie3::discard_tree_cache() { - for (auto& tree : m_trees) { - glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); - glDeleteTextures(1, &tree.time_of_day_texture); - glDeleteBuffers(1, &tree.vertex_buffer); - glDeleteBuffers(1, &tree.index_buffer); - glDeleteVertexArrays(1, &tree.vao); - if (tree.has_wind) { - glDeleteBuffers(1, &tree.wind_vertex_index_buffer); + for (int geo = 0; geo < 4; ++geo) { + for (auto& tree : m_trees[geo]) { + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + glDeleteTextures(1, &tree.time_of_day_texture); + glDeleteBuffers(1, &tree.vertex_buffer); + glDeleteBuffers(1, &tree.index_buffer); + glDeleteVertexArrays(1, &tree.vao); + if (tree.has_wind) { + glDeleteBuffers(1, &tree.wind_vertex_index_buffer); + } } - } - m_trees.clear(); + m_trees[geo].clear(); + } } void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { @@ -429,11 +439,12 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi if (!m_override_level) { m_has_level = setup_for_level(m_pc_port_data.level_name, render_state); } - render_all_trees(settings, render_state, prof); + render_all_trees(lod(), settings, render_state, prof); // todo render all... } -void Tie3::render_all_trees(const TfragRenderSettings& settings, +void Tie3::render_all_trees(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { Timer all_tree_timer; @@ -441,17 +452,18 @@ void Tie3::render_all_trees(const TfragRenderSettings& settings, 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++) { - render_tree(i, settings, render_state, prof); + for (u32 i = 0; i < m_trees[geom].size(); i++) { + render_tree(i, geom, settings, render_state, prof); } m_all_tree_time.add(all_tree_timer.getSeconds()); } void Tie3::render_tree_wind(int idx, + int geom, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { - auto& tree = m_trees.at(idx); + auto& tree = m_trees.at(geom).at(idx); if (tree.wind_draws->empty()) { return; } @@ -560,11 +572,12 @@ void Tie3::render_tree_wind(int idx, } void Tie3::render_tree(int idx, + int geom, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof) { Timer tree_timer; - auto& tree = m_trees.at(idx); + auto& tree = m_trees.at(geom).at(idx); tree.perf.draws = 0; tree.perf.verts = 0; tree.perf.full_draws = 0; @@ -704,7 +717,7 @@ void Tie3::render_tree(int idx, if (!m_hide_wind) { auto wind_prof = prof.make_scoped_child("wind"); - render_tree_wind(idx, settings, render_state, wind_prof); + render_tree_wind(idx, geom, settings, render_state, wind_prof); } glBindVertexArray(0); @@ -725,11 +738,11 @@ void Tie3::draw_debug_window() { ImGui::Checkbox("Hide Wind", &m_hide_wind); ImGui::SliderFloat("Wind Multiplier", &m_wind_multiplier, 0., 40.f); ImGui::Separator(); - for (u32 i = 0; i < m_trees.size(); i++) { - auto& perf = m_trees[i].perf; + for (u32 i = 0; i < m_trees[lod()].size(); i++) { + auto& perf = m_trees[lod()][i].perf; ImGui::Text("Tree: %d", i); ImGui::Text("index data bytes: %d", perf.index_upload); - ImGui::Text("time of days: %d", (int)m_trees[i].colors->size()); + ImGui::Text("time of days: %d", (int)m_trees[lod()][i].colors->size()); ImGui::Text("draw: %d, full: %d, verts: %d", perf.draws, perf.full_draws, perf.verts); ImGui::Text("wind draw: %d", perf.wind_draws); ImGui::Text("total: %.2f", perf.tree_time.get()); diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.h b/game/graphics/opengl_renderer/tfrag/Tie3.h index 197c6ad863..c65ac864f6 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.h +++ b/game/graphics/opengl_renderer/tfrag/Tie3.h @@ -2,6 +2,7 @@ #include +#include "game/graphics/gfx.h" #include "game/graphics/opengl_renderer/tfrag/tfrag_common.h" #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/pipelines/opengl.h" @@ -14,10 +15,12 @@ class Tie3 : public BucketRenderer { void draw_debug_window() override; ~Tie3(); - void render_all_trees(const TfragRenderSettings& settings, + void render_all_trees(int geom, + const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); void render_tree(int idx, + int geom, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); @@ -34,13 +37,19 @@ class Tie3 : public BucketRenderer { u32 pad2[3]; } m_wind_data; + int lod() const { return Gfx::g_global_settings.lod_tie; } + private: bool update_load(const tfrag3::Level* lev_data); void discard_tree_cache(); void render_tree_wind(int idx, + int geom, const TfragRenderSettings& settings, SharedRenderState* render_state, ScopedProfilerNode& prof); + + int m_geom = 0; + struct Tree { GLuint vertex_buffer; GLuint index_buffer; @@ -76,7 +85,7 @@ class Tie3 : public BucketRenderer { } perf; }; - std::vector m_trees; + std::array, 4> m_trees; // includes 4 lods! std::string m_level_name; const std::vector* m_textures; u64 m_load_id = -1; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 66dd4151d1..60cdc20132 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -126,6 +126,8 @@ static int gl_init(GfxSettings& settings) { } else { glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_FALSE); } + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); + glfwWindowHint(GLFW_SAMPLES, 4); return 0; } @@ -141,7 +143,6 @@ static std::shared_ptr gl_make_main_display(int width, int height, const char* title, GfxSettings& settings) { - glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); GLFWwindow* window = glfwCreateWindow(width, height, title, NULL, NULL); if (!window) { @@ -263,7 +264,6 @@ void render_game_frame(int width, int height, int lbox_width, int lbox_height) { options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler(); options.playing_from_dump = false; options.save_screenshot = g_gfx_data->debug_gui.get_screenshot_flag(); - options.screenshot_should_compress = g_gfx_data->debug_gui.screenshot_compress_flag(); if (options.save_screenshot) { options.screenshot_path = make_output_file_name(g_gfx_data->debug_gui.screenshot_name()); } diff --git a/game/graphics/texture/TexturePool.cpp b/game/graphics/texture/TexturePool.cpp index 3991df4584..d183b095d4 100644 --- a/game/graphics/texture/TexturePool.cpp +++ b/game/graphics/texture/TexturePool.cpp @@ -249,7 +249,7 @@ std::vector> TexturePool::convert_textures(const fmt::format( file_util::get_file_path({"debug_out", "textures", tpage_name, "{}-{}-{}.png"}), tex_idx, tex_name, mip_idx), - texture_record->data.data(), ww, hh, false); + texture_record->data.data(), ww, hh); } result.push_back(std::move(texture_record)); } diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index 38d4202f25..c958907c1e 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -826,6 +826,7 @@ void InitMachine_PCPort() { make_function_symbol_from_c("pc-set-window-size", (void*)Gfx::set_window_size); make_function_symbol_from_c("pc-set-letterbox", (void*)Gfx::set_letterbox); make_function_symbol_from_c("pc-set-fullscreen", (void*)Gfx::set_fullscreen); + make_function_symbol_from_c("pc-renderer-tree-set-lod", (void*)Gfx::SetLod); // discord rich presence make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc); diff --git a/goal_src/engine/debug/default-menu.gc b/goal_src/engine/debug/default-menu.gc index 1e2f5ddb1a..2c004fcd5d 100644 --- a/goal_src/engine/debug/default-menu.gc +++ b/goal_src/engine/debug/default-menu.gc @@ -4316,6 +4316,7 @@ ) (menu "Sizes" (function "640 x 480" #f (lambda () (set-size! *pc-settings* 640 480))) + (function "640 x 360" #f (lambda () (set-size! *pc-settings* 640 360))) (function "720 x 540" #f (lambda () (set-size! *pc-settings* 720 540))) (function "960 x 540" #f (lambda () (set-size! *pc-settings* 960 540))) (function "800 x 600" #f (lambda () (set-size! *pc-settings* 800 600))) @@ -4357,10 +4358,20 @@ ) (menu "Level of detail" (flag "PS2 LOD " #f ,(dm-lambda-boolean-flag (-> *pc-settings* ps2-lod-dist?))) - ;(int-var "LOD Tfrag" 0 dm-lod-int 0 1 #t 0 3) - ;(int-var "LOD Tie" 1 dm-lod-int 0 1 #t 0 3) + (int-var "LOD Tfrag" 0 dm-lod-int 0 1 #t 0 2) + (int-var "LOD Tie" 1 dm-lod-int 0 1 #t 0 3) ;(int-var "LOD Ocean" 2 dm-lod-int 0 1 #t 0 3) (int-var "LOD Actor" 3 dm-lod-int 0 1 #t 0 3) + (function "Best quality" #f (lambda () (set! (-> *pc-settings* lod-force-tfrag) 0) + (set! (-> *pc-settings* lod-force-tie) 0) + ;(set! (-> *pc-settings* lod-force-ocean) 0) + (set! (-> *pc-settings* lod-force-actor) 0) + )) + (function "Worst quality" #f (lambda () (set! (-> *pc-settings* lod-force-tfrag) 2) + (set! (-> *pc-settings* lod-force-tie) 3) + ;(set! (-> *pc-settings* lod-force-ocean) 2) + (set! (-> *pc-settings* lod-force-actor) 3) + )) ) ;(flag "Alt load boundaries" #f ,(dm-lambda-boolean-flag (-> *pc-settings* new-lb?))) (flag "All actors" #f ,(dm-lambda-boolean-flag (-> *pc-settings* force-actors?))) diff --git a/goal_src/engine/game/main.gc b/goal_src/engine/game/main.gc index 0af98447a4..6e570d4b64 100644 --- a/goal_src/engine/game/main.gc +++ b/goal_src/engine/game/main.gc @@ -755,8 +755,8 @@ (dotimes (i LEVEL_COUNT) (format *stdcon* "level ~D ~12A ~A~%" i (-> *level* level i name) (-> *level* level i display?)) ) - (format *stdcon* "music ~A (f: ~D/~D) sound ~A ~A~%" - (-> *setting-control* current music) (-> *setting-control* current sound-flava) (-> *setting-control* default sound-flava) + (format *stdcon* "music ~A (f: ~D/~S) sound ~A ~A~%" + (-> *setting-control* current music) (-> *setting-control* current sound-flava) (enum->string music-flava (-> *setting-control* default sound-flava)) *sound-bank-1* *sound-bank-2*) (let ((pos (target-pos 0))) (format *stdcon* "target ~m ~m ~m~%" (-> pos x) (-> pos y) (-> pos z)) diff --git a/goal_src/engine/gfx/eye-h.gc b/goal_src/engine/gfx/eye-h.gc index 2f8db18ab5..85bbcce824 100644 --- a/goal_src/engine/gfx/eye-h.gc +++ b/goal_src/engine/gfx/eye-h.gc @@ -6,7 +6,7 @@ ;; dgos: GAME, ENGINE (define-extern get-eye-block (function int int int)) -(define-extern merc-eye-anim (function manipy none)) +(define-extern merc-eye-anim (function process-drawable none)) ;; DECOMP BEGINS diff --git a/goal_src/engine/gfx/eye.gc b/goal_src/engine/gfx/eye.gc index b889b76df1..497e71d074 100644 --- a/goal_src/engine/gfx/eye.gc +++ b/goal_src/engine/gfx/eye.gc @@ -1115,7 +1115,7 @@ ) ) -(defun merc-eye-anim ((arg0 manipy)) +(defun merc-eye-anim ((arg0 process-drawable)) ; (let* ((s5-0 (-> arg0 draw mgeo header eye-ctrl)) ; (a0-1 (-> arg0 skel root-channel 0)) ; (s4-0 (-> a0-1 frame-group)) diff --git a/goal_src/engine/pc/pckernel.gc b/goal_src/engine/pc/pckernel.gc index c69f303d69..6d683717e0 100644 --- a/goal_src/engine/pc/pckernel.gc +++ b/goal_src/engine/pc/pckernel.gc @@ -135,6 +135,11 @@ ) ) + (when #t ;; (not (-> obj ps2-lod-dist?)) + (pc-renderer-tree-set-lod (pc-renderer-tree-type tfrag3) (-> obj lod-force-tfrag)) + (pc-renderer-tree-set-lod (pc-renderer-tree-type tie3) (-> obj lod-force-tie)) + ) + (none)) (defmethod update pc-settings ((obj pc-settings)) diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index b12552aedd..36d2220d76 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -306,6 +306,12 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; PC Port functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defenum pc-renderer-tree-type + (none 0) + (tfrag3 1) + (tie3 2) + (invalid)) + (define-extern __read-ee-timer (function uint)) (define-extern __mem-move (function pointer pointer uint none)) (define-extern __send-gfx-dma-chain (function object object none)) @@ -325,6 +331,7 @@ (define-extern pc-set-window-size (function int int none)) (define-extern pc-set-letterbox (function int int none)) (define-extern pc-set-fullscreen (function int int none)) +(define-extern pc-renderer-tree-set-lod (function pc-renderer-tree-type int none)) (define-extern pc-discord-rpc-update (function discord-info none)) (define-extern pc-discord-rpc-set (function int none))