diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index d850dd720b..70c527eb9a 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -237,6 +237,54 @@ void CollisionMesh::serialize(Serializer& ser) { ser.from_pod_vector(&vertices); } +void MercDraw::serialize(Serializer& ser) { + ser.from_ptr(&mode); + ser.from_ptr(&tree_tex_id); + ser.from_ptr(&first_index); + ser.from_ptr(&index_count); + ser.from_ptr(&num_triangles); +} + +void MercEffect::serialize(Serializer& ser) { + if (ser.is_saving()) { + ser.save(draws.size()); + } else { + draws.resize(ser.load()); + } + for (auto& draw : draws) { + draw.serialize(ser); + } +} + +void MercModel::serialize(Serializer& ser) { + ser.from_str(&name); + if (ser.is_saving()) { + ser.save(effects.size()); + } else { + effects.resize(ser.load()); + } + for (auto& effect : effects) { + effect.serialize(ser); + } + ser.from_ptr(&scale_xyz); + ser.from_ptr(&max_draws); + ser.from_ptr(&max_bones); +} + +void MercModelGroup::serialize(Serializer& ser) { + if (ser.is_saving()) { + ser.save(models.size()); + } else { + models.resize(ser.load()); + } + for (auto& model : models) { + model.serialize(ser); + } + + ser.from_pod_vector(&indices); + ser.from_pod_vector(&vertices); +} + void Level::serialize(Serializer& ser) { ser.from_ptr(&version); if (ser.is_loading() && version != TFRAG3_VERSION) { @@ -287,6 +335,7 @@ void Level::serialize(Serializer& ser) { } collision.serialize(ser); + merc_data.serialize(ser); ser.from_ptr(&version2); if (ser.is_loading() && version2 != TFRAG3_VERSION) { @@ -358,6 +407,10 @@ std::array Level::get_memory_usage() c result[SHRUB_IND] += sizeof(u32) * shrub_tree.indices.size(); } + // merc + result[MERC_INDEX] += merc_data.indices.size() * sizeof(u32); + result[MERC_VERT] += merc_data.vertices.size() * sizeof(MercVertex); + // collision result[COLLISION] += sizeof(CollisionMesh::Vertex) * collision.vertices.size(); diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index d0b953455f..2354eeac2c 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -45,12 +45,15 @@ enum MemoryUsageCategory { SHRUB_VERT, SHRUB_IND, + MERC_VERT, + MERC_INDEX, + COLLISION, NUM_CATEGORIES }; -constexpr int TFRAG3_VERSION = 17; +constexpr int TFRAG3_VERSION = 19; // These vertices should be uploaded to the GPU at load time and don't change struct PreloadedVertex { @@ -323,6 +326,59 @@ struct CollisionMesh { void serialize(Serializer& ser); }; +// MERC + +struct MercVertex { + float pos[3]; + float pad0; + + float normal[3]; + float pad1; + + float weights[3]; + float pad2; + + float st[2]; + + u8 rgba[4]; + u8 mats[3]; + u8 pad3; +}; +static_assert(sizeof(MercVertex) == 64); + +struct MercDraw { + DrawMode mode; + u32 tree_tex_id = 0; // the texture that should be bound for the draw + + u32 first_index; + u32 index_count; + u32 num_triangles; + void serialize(Serializer& ser); +}; + +struct MercEffect { + std::vector draws; + void serialize(Serializer& ser); +}; + +struct MercModel { + std::string name; + std::vector effects; + float scale_xyz; + u32 max_draws; + u32 max_bones; + void serialize(Serializer& ser); +}; + +struct MercModelGroup { + std::vector vertices; + std::vector indices; + std::vector models; + void serialize(Serializer& ser); +}; + +// + constexpr int TFRAG_GEOS = 3; constexpr int TIE_GEOS = 4; @@ -334,6 +390,7 @@ struct Level { std::array, TIE_GEOS> tie_trees; std::vector shrub_trees; CollisionMesh collision; + MercModelGroup merc_data; u16 version2 = TFRAG3_VERSION; void serialize(Serializer& ser); diff --git a/common/dma/dma.cpp b/common/dma/dma.cpp index e05a2cdd72..7ff1b6799a 100644 --- a/common/dma/dma.cpp +++ b/common/dma/dma.cpp @@ -79,6 +79,9 @@ std::string VifCode::print() { case Kind::DIRECTHL: result = "DIRECTHL"; break; + case Kind::PC_PORT: + result = "PC_PORT"; + break; case Kind::UNPACK_V4_8: { VifCodeUnpack up(*this); result = fmt::format("UNPACK-V4-8: {} addr: {} us: {} tops: {}", num, up.addr_qw, diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 7dd1dc9cb1..cb0ab3ebbe 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -53,6 +53,7 @@ add_library( level_extractor/BspHeader.cpp level_extractor/extract_collide_frags.cpp + level_extractor/extract_common.cpp level_extractor/extract_level.cpp level_extractor/extract_merc.cpp level_extractor/extract_tfrag.cpp diff --git a/decompiler/level_extractor/MercData.cpp b/decompiler/level_extractor/MercData.cpp index e989435b95..6e9c404336 100644 --- a/decompiler/level_extractor/MercData.cpp +++ b/decompiler/level_extractor/MercData.cpp @@ -5,6 +5,7 @@ namespace decompiler { void MercCtrlHeader::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) { + st_magic = read_plain_data_field(tr, "st-magic", dts); xyz_scale = read_plain_data_field(tr, "xyz-scale", dts); st_out_a = read_plain_data_field(tr, "st-out-a", dts); st_out_b = read_plain_data_field(tr, "st-out-b", dts); diff --git a/decompiler/level_extractor/extract_common.cpp b/decompiler/level_extractor/extract_common.cpp new file mode 100644 index 0000000000..7a4d4b82c2 --- /dev/null +++ b/decompiler/level_extractor/extract_common.cpp @@ -0,0 +1,44 @@ +#include "extract_common.h" + +u32 clean_up_vertex_indices(std::vector& idx) { + std::vector fixed; + u32 num_tris = 0; + + bool looking_for_start = true; + size_t i_of_start; + for (size_t i = 0; i < idx.size(); i++) { + if (looking_for_start) { + if (idx[i] != UINT32_MAX) { + looking_for_start = false; + i_of_start = i; + } + } else { + if (idx[i] == UINT32_MAX) { + looking_for_start = true; + size_t num_verts = i - i_of_start; + if (num_verts >= 3) { + if (!fixed.empty()) { + fixed.push_back(UINT32_MAX); + } + fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + i); + num_tris += (num_verts - 2); + } + } + } + } + + if (!looking_for_start) { + size_t num_verts = idx.size() - i_of_start; + if (num_verts >= 3) { + if (!fixed.empty()) { + fixed.push_back(UINT32_MAX); + } + fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + idx.size()); + num_tris += (num_verts - 2); + } + } + + idx = std::move(fixed); + + return num_tris; +} \ No newline at end of file diff --git a/decompiler/level_extractor/extract_common.h b/decompiler/level_extractor/extract_common.h new file mode 100644 index 0000000000..55a91495f2 --- /dev/null +++ b/decompiler/level_extractor/extract_common.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include "common/common_types.h" + +u32 clean_up_vertex_indices(std::vector& idx); \ No newline at end of file diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 92022eb228..f63931a31c 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -80,7 +80,9 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) { {"shrub-colors", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_TIME_OF_DAY]}, {"shrub-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_VERT]}, {"shrub-ind", memory_use_by_category[tfrag3::MemoryUsageCategory::SHRUB_IND]}, - {"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]}}; + {"collision", memory_use_by_category[tfrag3::MemoryUsageCategory::COLLISION]}, + {"merc-vert", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_VERT]}, + {"merc-idx", memory_use_by_category[tfrag3::MemoryUsageCategory::MERC_INDEX]}}; for (auto& known : known_categories) { total_accounted += known.second; } @@ -263,6 +265,8 @@ void extract_common(const ObjectFileDB& db, tfrag_level.serialize(ser); auto compressed = compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second); + + fmt::print("stats for {}\n", dgo_name); print_memory_usage(tfrag_level, 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); @@ -293,6 +297,7 @@ void extract_from_level(const ObjectFileDB& db, level_data.serialize(ser); auto compressed = compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second); + fmt::print("stats for {}\n", dgo_name); print_memory_usage(level_data, 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); diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index 1bb59f9f57..ba7f0bb6d6 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -1,6 +1,7 @@ #include "extract_merc.h" #include "decompiler/util/goal_data_reader.h" #include "decompiler/level_extractor/MercData.h" +#include "decompiler/level_extractor/extract_common.h" #include "common/util/FileUtil.h" #include "common/util/colors.h" @@ -228,6 +229,9 @@ DrawMode process_draw_mode(const MercShader& info) { // check these mode.disable_ab(); mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST); + mode.set_tcc(info.tex0.tcc()); + mode.set_decal(info.tex0.tfx() == GsTex0::TextureFunction::DECAL); + mode.set_filt_enable(info.tex1.mmag()); // the alpha matters (maybe?) update_mode_from_alpha1(info.alpha, mode); @@ -334,8 +338,8 @@ void handle_frag(const std::string& debug_name, if (vtx.kind == 1) { vtx.skel_mats[0] = state.vu1_matrix_slots.at(vu1_addr_to_matrix_slot(mat0_addr)); - vtx.skel_mats[1] = -1; - vtx.skel_mats[2] = -1; + vtx.skel_mats[1] = 0; + vtx.skel_mats[2] = 0; vtx.mat_weights[0] = 1.f; vtx.mat_weights[1] = 0.f; vtx.mat_weights[2] = 0.f; @@ -351,7 +355,7 @@ void handle_frag(const std::string& debug_name, } vtx.skel_mats[0] = state.vu1_matrix_slots.at(prev_mat0); vtx.skel_mats[1] = state.vu1_matrix_slots.at(prev_mat1); - vtx.skel_mats[2] = -1; + vtx.skel_mats[2] = 0; if (m0 != 0x7f && i != mat1_cnt) { if (perc_toggle) { @@ -436,6 +440,7 @@ void handle_frag(const std::string& debug_name, vtx.dst0 = float_as_u32(v1.x()) - 371; // xtop to output buffer offset vtx.dst1 = float_as_u32(v1.y()) - 371; vtx.rgba = frag.unsigned_four_including_header.at(rgba_ptr); + vtx.st = math::Vector2f(v2.x(), v2.y()); // crazy flag logic to set adc s16 mat1_flag = mat1; @@ -812,6 +817,41 @@ ConvertedMercEffect convert_merc_effect(const MercEffect& input_effect, return result; } +u8 convert_mat(int in) { + if (in >= 0) { + return in; + } else { + return 0; + } +} + +tfrag3::MercVertex convert_vertex(const MercUnpackedVtx& vtx) { + tfrag3::MercVertex out; + out.pos[0] = vtx.pos[0]; + out.pos[1] = vtx.pos[1]; + out.pos[2] = vtx.pos[2]; + out.pad0 = 0; + out.normal[0] = vtx.nrm[0]; + out.normal[1] = vtx.nrm[1]; + out.normal[2] = vtx.nrm[2]; + out.pad1 = 0; + out.weights[0] = vtx.mat_weights[0]; + out.weights[1] = vtx.mat_weights[1]; + out.weights[2] = vtx.mat_weights[2]; + out.pad2 = 0; + out.st[0] = vtx.st[0]; + out.st[1] = vtx.st[1]; + out.rgba[0] = vtx.rgba[0]; + out.rgba[1] = vtx.rgba[1]; + out.rgba[2] = vtx.rgba[2]; + out.rgba[3] = vtx.rgba[3]; + out.mats[0] = convert_mat(vtx.skel_mats[0]); + out.mats[1] = convert_mat(vtx.skel_mats[1]); + out.mats[2] = convert_mat(vtx.skel_mats[2]); + out.pad3 = 0; + return out; +} + /*! * Top-level merc extraction */ @@ -819,7 +859,7 @@ void extract_merc(const ObjectFileData& ag_data, const TextureDB& tex_db, const DecompilerTypeSystem& dts, const std::vector& map, - tfrag3::Level& /*out*/, + tfrag3::Level& out, bool dump_level) { if (dump_level) { file_util::create_dir_if_needed(file_util::get_file_path({"debug_out/merc"})); @@ -835,11 +875,113 @@ void extract_merc(const ObjectFileData& ag_data, } // extract draws. this does no regrouping yet. - std::vector all_effects; + std::vector> all_effects; for (size_t ci = 0; ci < ctrls.size(); ci++) { + auto& effects_in_ctrl = all_effects.emplace_back(); for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) { - all_effects.push_back(convert_merc_effect(ctrls[ci].effects[ei], ctrls[ci].header, tex_db, - map, ctrls[ci].name, ci, ei, dump_level)); + effects_in_ctrl.push_back(convert_merc_effect(ctrls[ci].effects[ei], ctrls[ci].header, tex_db, + map, ctrls[ci].name, ci, ei, dump_level)); + } + } + + // convert to PC format + // first pass, before merging indices + u32 first_model = out.merc_data.models.size(); + std::vector>>> indices_temp; // ctrl, effect, draw, vtx + for (size_t ci = 0; ci < ctrls.size(); ci++) { + indices_temp.emplace_back(); + auto& pc_ctrl = out.merc_data.models.emplace_back(); + auto& ctrl = ctrls[ci]; + + pc_ctrl.name = ctrl.name; + pc_ctrl.scale_xyz = ctrl.header.xyz_scale; + pc_ctrl.max_draws = 0; + pc_ctrl.max_bones = 0; + + for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) { + indices_temp[ci].emplace_back(); + auto& pc_effect = pc_ctrl.effects.emplace_back(); + auto& effect = all_effects[ci][ei]; + u32 first_vertex = out.merc_data.vertices.size(); + for (auto& vtx : effect.vertices) { + auto cvtx = convert_vertex(vtx); + out.merc_data.vertices.push_back(cvtx); + for (int i = 0; i < 3; i++) { + pc_ctrl.max_bones = std::max(pc_ctrl.max_bones, (u32)cvtx.mats[i]); + } + } + + // can do two types of de-duplication: toggling back and forth shaders and matrices + std::map draw_mode_dedup; + + for (auto& draw : effect.draws) { + pc_ctrl.max_draws++; + indices_temp[ci][ei].emplace_back(); + // find draw to add to, or create a new one + const auto& existing = draw_mode_dedup.find(draw.state.merc_draw_mode.as_u64()); + tfrag3::MercDraw* pc_draw = nullptr; + u64 pc_draw_idx = -1; + if (existing == draw_mode_dedup.end()) { + pc_draw_idx = pc_effect.draws.size(); + draw_mode_dedup[draw.state.merc_draw_mode.as_u64()] = pc_draw_idx; + pc_draw = &pc_effect.draws.emplace_back(); + pc_draw->mode = draw.state.merc_draw_mode.mode; + + u32 idx_in_level_texture = UINT32_MAX; + for (u32 i = 0; i < out.textures.size(); i++) { + if (out.textures[i].combo_id == draw.state.merc_draw_mode.pc_combo_tex_id) { + idx_in_level_texture = i; + break; + } + } + + if (idx_in_level_texture == UINT32_MAX) { + // not added to level, add it + auto tex_it = tex_db.textures.find(draw.state.merc_draw_mode.pc_combo_tex_id); + if (tex_it == tex_db.textures.end()) { + ASSERT(false); + } else { + idx_in_level_texture = out.textures.size(); + auto& new_tex = out.textures.emplace_back(); + new_tex.combo_id = draw.state.merc_draw_mode.pc_combo_tex_id; + new_tex.w = tex_it->second.w; + new_tex.h = tex_it->second.h; + new_tex.debug_name = tex_it->second.name; + new_tex.debug_tpage_name = tex_db.tpage_names.at(tex_it->second.page); + new_tex.data = tex_it->second.rgba_bytes; + } + } + + pc_draw->tree_tex_id = idx_in_level_texture; + } else { + pc_draw_idx = existing->second; + pc_draw = &pc_effect.draws.at(pc_draw_idx); + } + + for (auto idx : draw.indices) { + if (idx == UINT32_MAX) { + indices_temp[ci][ei][pc_draw_idx].push_back(idx); + } else { + indices_temp[ci][ei][pc_draw_idx].push_back(idx + first_vertex); + } + } + } + } + } + + // merge indices + for (size_t ci = 0; ci < ctrls.size(); ci++) { + auto& pc_ctrl = out.merc_data.models.at(ci + first_model); + for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) { + auto& pc_effect = pc_ctrl.effects.at(ei); + for (size_t di = 0; di < pc_effect.draws.size(); di++) { + auto& pc_draw = pc_effect.draws.at(di); + auto& inds = indices_temp[ci][ei][di]; + pc_draw.num_triangles = clean_up_vertex_indices(inds); + pc_draw.first_index = out.merc_data.indices.size(); + pc_draw.index_count = inds.size(); + out.merc_data.indices.insert(out.merc_data.indices.end(), inds.begin(), inds.end()); + } } } } diff --git a/decompiler/level_extractor/extract_shrub.cpp b/decompiler/level_extractor/extract_shrub.cpp index dd56d633a8..560bccf64a 100644 --- a/decompiler/level_extractor/extract_shrub.cpp +++ b/decompiler/level_extractor/extract_shrub.cpp @@ -1,7 +1,7 @@ #include #include "extract_shrub.h" - +#include "decompiler/level_extractor/extract_common.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" #include "common/util/FileUtil.h" @@ -395,49 +395,6 @@ std::string dump_full_to_obj(const std::vector& protos) { return result; } -u32 clean_up_vertex_indices(std::vector& idx) { - std::vector fixed; - u32 num_tris = 0; - - bool looking_for_start = true; - size_t i_of_start; - for (size_t i = 0; i < idx.size(); i++) { - if (looking_for_start) { - if (idx[i] != UINT32_MAX) { - looking_for_start = false; - i_of_start = i; - } - } else { - if (idx[i] == UINT32_MAX) { - looking_for_start = true; - size_t num_verts = i - i_of_start; - if (num_verts >= 3) { - if (!fixed.empty()) { - fixed.push_back(UINT32_MAX); - } - fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + i); - num_tris += (num_verts - 2); - } - } - } - } - - if (!looking_for_start) { - size_t num_verts = idx.size() - i_of_start; - if (num_verts >= 3) { - if (!fixed.empty()) { - fixed.push_back(UINT32_MAX); - } - fixed.insert(fixed.end(), idx.begin() + i_of_start, idx.begin() + idx.size()); - num_tris += (num_verts - 2); - } - } - - idx = std::move(fixed); - - return num_tris; -} - void make_draws(tfrag3::Level& lev, tfrag3::ShrubTree& tree_out, const std::vector& protos, diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index b5f80b3f0b..487da4640d 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -85,6 +85,7 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/background/Tfrag3.cpp graphics/opengl_renderer/background/TFragment.cpp graphics/opengl_renderer/background/Tie3.cpp + graphics/opengl_renderer/foreground/Merc2.cpp graphics/opengl_renderer/foreground/Generic2.cpp graphics/opengl_renderer/foreground/Generic2_DMA.cpp graphics/opengl_renderer/foreground/Generic2_Build.cpp diff --git a/game/graphics/opengl_renderer/BucketRenderer.cpp b/game/graphics/opengl_renderer/BucketRenderer.cpp index 3a1d7bd568..68a55819f3 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.cpp +++ b/game/graphics/opengl_renderer/BucketRenderer.cpp @@ -89,4 +89,16 @@ void RenderMux::draw_debug_window() { ImGui::ListBox("Pick", &m_render_idx, m_name_str_ptrs.data(), m_renderers.size()); ImGui::Separator(); m_renderers[m_render_idx]->draw_debug_window(); +} + +void RenderMux::init_textures(TexturePool& tp) { + for (auto& rend : m_renderers) { + rend->init_textures(tp); + } +} + +void RenderMux::init_shaders(ShaderLibrary& sl) { + for (auto& rend : m_renderers) { + rend->init_shaders(sl); + } } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 6a45d673ee..5e10581070 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -40,7 +40,8 @@ struct SharedRenderState { bool use_direct2 = true; math::Vector fog_color; float fog_intensity = 1.f; - bool no_multidraw = true; + bool no_multidraw = false; + bool merc2 = true; void reset(); bool has_pc_data = false; @@ -87,6 +88,9 @@ class RenderMux : public BucketRenderer { std::vector> renderers); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; + void init_shaders(ShaderLibrary&) override; + void init_textures(TexturePool&) override; + void set_idx(u32 i) { m_render_idx = i; }; private: std::vector> m_renderers; diff --git a/game/graphics/opengl_renderer/Loader.cpp b/game/graphics/opengl_renderer/Loader.cpp index 4bdbaffaa5..fe4697827f 100644 --- a/game/graphics/opengl_renderer/Loader.cpp +++ b/game/graphics/opengl_renderer/Loader.cpp @@ -39,8 +39,8 @@ const Loader::LevelData* Loader::get_tfrag3_level(const std::string& level_name) if (existing == m_loaded_tfrag3_levels.end()) { return nullptr; } else { - existing->second.frames_since_last_used = 0; - return &existing->second.data; + existing->second->frames_since_last_used = 0; + return existing->second.get(); } } @@ -83,8 +83,8 @@ std::vector Loader::get_in_use_levels() { std::unique_lock lk(m_loader_mutex); for (auto& lev : m_loaded_tfrag3_levels) { - if (lev.second.frames_since_last_used < 5) { - result.push_back(&lev.second.data); + if (lev.second->frames_since_last_used < 5) { + result.push_back(lev.second.get()); } } return result; @@ -151,8 +151,8 @@ void Loader::loader_thread() { // grab the lock again lk.lock(); // move this level to "initializing" state. - m_initializing_tfrag3_levels[lev].data = {}; // reset load state - m_initializing_tfrag3_levels[lev].data.level = std::move(result); + m_initializing_tfrag3_levels[lev] = std::make_unique(); // reset load state + m_initializing_tfrag3_levels[lev]->level = std::move(result); m_level_to_load = ""; m_file_load_done_cv.notify_all(); } @@ -168,10 +168,14 @@ void Loader::load_common(TexturePool& tex_pool, const std::string& name) { auto decomp_data = compression::decompress_zstd(data.data(), data.size()); Serializer ser(decomp_data.data(), decomp_data.size()); - m_common_level.serialize(ser); - for (auto& tex : m_common_level.textures) { - add_texture(tex_pool, tex, true); + m_common_level.level = std::make_unique(); + m_common_level.level->serialize(ser); + for (auto& tex : m_common_level.level->textures) { + m_common_level.textures.push_back(add_texture(tex_pool, tex, true)); } + + Timer tim; + init_merc(tim, m_common_level); } /*! @@ -490,11 +494,33 @@ bool Loader::init_tie(Timer& timer, LevelData& data) { } bool Loader::init_collide(Timer& /*timer*/, LevelData& data) { + Timer t; glGenBuffers(1, &data.collide_vertices); glBindBuffer(GL_ARRAY_BUFFER, data.collide_vertices); glBufferData(GL_ARRAY_BUFFER, data.level->collision.vertices.size() * sizeof(tfrag3::CollisionMesh::Vertex), data.level->collision.vertices.data(), GL_STATIC_DRAW); + fmt::print("init_collide took {:.2f} ms\n", t.getMs()); + return true; +} + +bool Loader::init_merc(Timer& /*timer*/, LevelData& data) { + Timer t; + glGenBuffers(1, &data.merc_indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.merc_indices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.level->merc_data.indices.size() * sizeof(u32), + data.level->merc_data.indices.data(), GL_STATIC_DRAW); + + glGenBuffers(1, &data.merc_vertices); + glBindBuffer(GL_ARRAY_BUFFER, data.merc_vertices); + glBufferData(GL_ARRAY_BUFFER, data.level->merc_data.vertices.size() * sizeof(tfrag3::MercVertex), + data.level->merc_data.vertices.data(), GL_STATIC_DRAW); + + for (auto& model : data.level->merc_data.models) { + data.merc_model_lookup[model.name] = &model; + m_all_merc_models[model.name].push_back({&model, data.load_id, &data}); + } + fmt::print("init_merc took {:.2f} ms\n", t.getMs()); return true; } @@ -584,7 +610,7 @@ void Loader::update(TexturePool& texture_pool) { // only main thread can touch this. for (auto& lev : m_loaded_tfrag3_levels) { - lev.second.frames_since_last_used++; + lev.second->frames_since_last_used++; } bool did_gpu_stuff = false; @@ -598,22 +624,25 @@ void Loader::update(TexturePool& texture_pool) { if (it != m_initializing_tfrag3_levels.end()) { std::string name = it->first; auto& lev = it->second; + it->second->load_id = m_id++; + // we're the only place that erases, so it's okay to unlock and hold a reference lk.unlock(); - if (!lev.data.tfrag_load_done) { + if (!lev->tfrag_load_done) { did_gpu_stuff = true; } - if (upload_textures(loader_timer, lev.data, texture_pool)) { - if (init_tie(loader_timer, lev.data)) { - if (init_tfrag(loader_timer, lev.data)) { - if (init_shrub(loader_timer, lev.data)) { - if (init_collide(loader_timer, lev.data)) { - // we're done! lock before removing from loaded. - lk.lock(); - it->second.data.load_id = m_id++; + if (upload_textures(loader_timer, *lev, texture_pool)) { + if (init_tie(loader_timer, *lev)) { + if (init_tfrag(loader_timer, *lev)) { + if (init_shrub(loader_timer, *lev)) { + if (init_collide(loader_timer, *lev)) { + if (init_merc(loader_timer, *lev)) { + // we're done! lock before removing from loaded. + lk.lock(); - m_loaded_tfrag3_levels[name] = std::move(lev); - m_initializing_tfrag3_levels.erase(it); + m_loaded_tfrag3_levels[name] = std::move(lev); + m_initializing_tfrag3_levels.erase(it); + } } } } @@ -625,19 +654,19 @@ void Loader::update(TexturePool& texture_pool) { if (!did_gpu_stuff) { // try to remove levels. if (m_loaded_tfrag3_levels.size() >= 3) { - for (const auto& lev : m_loaded_tfrag3_levels) { - if (lev.second.frames_since_last_used > 180) { + for (auto& lev : m_loaded_tfrag3_levels) { + if (lev.second->frames_since_last_used > 180) { std::unique_lock lk(texture_pool.mutex()); fmt::print("------------------------- PC unloading {}\n", lev.first); - for (size_t i = 0; i < lev.second.data.level->textures.size(); i++) { - auto& tex = lev.second.data.level->textures[i]; + for (size_t i = 0; i < lev.second->level->textures.size(); i++) { + auto& tex = lev.second->level->textures[i]; if (tex.load_to_pool) { texture_pool.unload_texture(PcTextureId::from_combo_id(tex.combo_id), - lev.second.data.textures.at(i)); + lev.second->textures.at(i)); } } lk.unlock(); - for (auto tex : lev.second.data.textures) { + for (auto tex : lev.second->textures) { if (EXTRA_TEX_DEBUG) { for (auto& slot : texture_pool.all_textures()) { if (slot.source) { @@ -652,7 +681,7 @@ void Loader::update(TexturePool& texture_pool) { glDeleteTextures(1, &tex); } - for (auto& tie_geo : lev.second.data.tie_data) { + for (auto& tie_geo : lev.second->tie_data) { for (auto& tie_tree : tie_geo) { glDeleteBuffers(1, &tie_tree.vertex_buffer); if (tie_tree.has_wind) { @@ -661,13 +690,23 @@ void Loader::update(TexturePool& texture_pool) { } } - for (auto& tfrag_geo : lev.second.data.tfrag_vertex_data) { + for (auto& tfrag_geo : lev.second->tfrag_vertex_data) { for (auto& tfrag_buff : tfrag_geo) { glDeleteBuffers(1, &tfrag_buff); } } - glDeleteBuffers(1, &lev.second.data.collide_vertices); + glDeleteBuffers(1, &lev.second->collide_vertices); + glDeleteBuffers(1, &lev.second->merc_vertices); + glDeleteBuffers(1, &lev.second->merc_indices); + + for (auto& model : lev.second->level->merc_data.models) { + auto& mercs = m_all_merc_models.at(model.name); + MercRef ref{&model, lev.second->load_id}; + auto it = std::find(mercs.begin(), mercs.end(), ref); + ASSERT_MSG(it != mercs.end(), fmt::format("missing merc: {}\n", model.name)); + mercs.erase(it); + } m_loaded_tfrag3_levels.erase(lev.first); break; @@ -680,3 +719,14 @@ void Loader::update(TexturePool& texture_pool) { fmt::print("Loader::update slow setup: {:.1f}ms\n", loader_timer.getMs()); } } + +std::optional Loader::get_merc_model(const char* model_name) { + // don't think we need to lock here... + const auto& it = m_all_merc_models.find(model_name); + if (it != m_all_merc_models.end() && !it->second.empty()) { + // it->second.front().parent_level->frames_since_last_used = 0; + return it->second.front(); + } else { + return std::nullopt; + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Loader.h b/game/graphics/opengl_renderer/Loader.h index 70577752b0..666035517c 100644 --- a/game/graphics/opengl_renderer/Loader.h +++ b/game/graphics/opengl_renderer/Loader.h @@ -33,6 +33,10 @@ class Loader { std::vector shrub_vertex_data; GLuint collide_vertices; + GLuint merc_vertices; + GLuint merc_indices; + std::unordered_map merc_model_lookup; + // internal load state bool tie_opengl_created = false; bool tie_verts_done = false; @@ -52,19 +56,26 @@ class Loader { bool shrub_load_done = false; u32 shrub_next_tree = 0; u32 shrub_next_vert = 0; + + int frames_since_last_used = 0; + }; + + struct MercRef { + const tfrag3::MercModel* model = nullptr; + u64 load_id = 0; + const LevelData* level = nullptr; + bool operator==(const MercRef& other) const { + return model == other.model && load_id == other.load_id; + } }; const LevelData* get_tfrag3_level(const std::string& level_name); + std::optional get_merc_model(const char* model_name); void load_common(TexturePool& tex_pool, const std::string& name); void set_want_levels(const std::vector& levels); std::vector get_in_use_levels(); private: - struct Level { - LevelData data; - int frames_since_last_used = 0; - }; - void loader_thread(); u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common); @@ -73,11 +84,12 @@ class Loader { bool init_tfrag(Timer& timer, LevelData& data); bool init_shrub(Timer& timer, LevelData& data); bool init_collide(Timer& timer, LevelData& data); + bool init_merc(Timer& timer, LevelData& data); // used by game and loader thread - std::unordered_map m_initializing_tfrag3_levels; + std::unordered_map> m_initializing_tfrag3_levels; - tfrag3::Level m_common_level; + LevelData m_common_level; std::string m_level_to_load; @@ -89,7 +101,9 @@ class Loader { uint64_t m_id = 0; // used only by game thread - std::unordered_map m_loaded_tfrag3_levels; + std::unordered_map> m_loaded_tfrag3_levels; + + std::unordered_map> m_all_merc_models; std::vector m_desired_levels; }; diff --git a/game/graphics/opengl_renderer/MercRenderer.cpp b/game/graphics/opengl_renderer/MercRenderer.cpp index 1411958d00..96303bd271 100644 --- a/game/graphics/opengl_renderer/MercRenderer.cpp +++ b/game/graphics/opengl_renderer/MercRenderer.cpp @@ -59,10 +59,12 @@ void MercRenderer::render(DmaFollower& dma, m_direct.flush_pending(render_state, prof); } +namespace { bool tag_is_nothing_next(const DmaFollower& dma) { return dma.current_tag().kind == DmaTag::Kind::NEXT && dma.current_tag().qwc == 0 && dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0; } +} // namespace void MercRenderer::unpack32(const VifCodeUnpack& up, const u8* data, u32 imm) { ASSERT(!up.is_unsigned); @@ -150,13 +152,19 @@ void MercRenderer::handle_merc_chain(DmaFollower& dma, auto init = dma.read_and_advance(); + // skip pc port stuff + if (init.vifcode1().kind == VifCode::Kind::PC_PORT) { + dma.read_and_advance(); + init = dma.read_and_advance(); + } ASSERT(init.vifcode0().kind == VifCode::Kind::STROW); ASSERT(init.size_bytes == 16); m_vif.row[0] = init.vif1(); memcpy(m_vif.row + 1, init.data, 12); - u32 extra; - memcpy(&extra, init.data + 12, 4); - ASSERT(extra == 0); + // now used in pc renderer. + // u32 extra; + // memcpy(&extra, init.data + 12, 4); + // ASSERT(extra == 0); DmaTransfer next; bool setting_up = true; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 060f530fdf..d37b8f06e9 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -16,6 +16,7 @@ #include "game/graphics/opengl_renderer/EyeRenderer.h" #include "game/graphics/opengl_renderer/ShadowRenderer.h" #include "game/graphics/opengl_renderer/foreground/Generic2.h" +#include "game/graphics/opengl_renderer/foreground/Merc2.h" #include "game/graphics/opengl_renderer/ocean/OceanMidAndFar.h" #include "game/graphics/opengl_renderer/ocean/OceanNear.h" @@ -68,6 +69,15 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool, init_bucket_renderers(); } +void OpenGLRenderer::init_merc_renderer(const std::string& name, BucketId id) { + std::vector> merc_renderers; + merc_renderers.push_back(std::make_unique(name, id)); + merc_renderers.push_back(std::make_unique(name, id)); + + m_mercs.push_back( + init_bucket_renderer(name, BucketCategory::MERC, id, std::move(merc_renderers))); +} + /*! * Construct bucket renderers. We can specify different renderers for different buckets */ @@ -106,8 +116,7 @@ void OpenGLRenderer::init_bucket_renderers() { // 9 : TIE_LEVEL0 init_bucket_renderer("l0-tfrag-tie", BucketCategory::TIE, BucketId::TIE_LEVEL0, 0); // 10 : MERC_TFRAG_TEX_LEVEL0 - init_bucket_renderer("l0-tfrag-merc", BucketCategory::MERC, - BucketId::MERC_TFRAG_TEX_LEVEL0); + init_merc_renderer("l0-tfrag-merc", BucketId::MERC_TFRAG_TEX_LEVEL0); // 11 : GMERC_TFRAG_TEX_LEVEL0 init_bucket_renderer("l0-tfrag-generic", BucketCategory::GENERIC, BucketId::GENERIC_TFRAG_TEX_LEVEL0, 1500000, 10000, 10000, 800); @@ -126,8 +135,7 @@ void OpenGLRenderer::init_bucket_renderers() { // 16 : TIE_LEVEL1 init_bucket_renderer("l1-tfrag-tie", BucketCategory::TIE, BucketId::TIE_LEVEL1, 1); // 17 : MERC_TFRAG_TEX_LEVEL1 - init_bucket_renderer("l1-tfrag-merc", BucketCategory::MERC, - BucketId::MERC_TFRAG_TEX_LEVEL1); + init_merc_renderer("l1-tfrag-merc", BucketId::MERC_TFRAG_TEX_LEVEL1); // 18 : GMERC_TFRAG_TEX_LEVEL1 init_bucket_renderer("l1-tfrag-generic", BucketCategory::GENERIC, BucketId::GENERIC_TFRAG_TEX_LEVEL1, 1500000, 10000, 10000, 800); @@ -194,8 +202,7 @@ void OpenGLRenderer::init_bucket_renderers() { BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1); // 44 - init_bucket_renderer("common-alpha-merc", BucketCategory::MERC, - BucketId::MERC_AFTER_ALPHA); + init_merc_renderer("common-alpha-merc", BucketId::MERC_AFTER_ALPHA); init_bucket_renderer("common-alpha-generic", BucketCategory::GENERIC, BucketId::GENERIC_ALPHA); // 46 @@ -206,8 +213,7 @@ void OpenGLRenderer::init_bucket_renderers() { //----------------------- init_bucket_renderer("l0-pris-tex", BucketCategory::TEX, BucketId::PRIS_TEX_LEVEL0); // 48 - init_bucket_renderer("l0-pris-merc", BucketCategory::MERC, - BucketId::MERC_PRIS_LEVEL0); // 49 + init_merc_renderer("l0-pris-merc", BucketId::MERC_PRIS_LEVEL0); // 49 init_bucket_renderer("l0-pris-generic", BucketCategory::GENERIC, BucketId::GENERIC_PRIS_LEVEL0); // 50 @@ -216,16 +222,16 @@ void OpenGLRenderer::init_bucket_renderers() { //----------------------- init_bucket_renderer("l1-pris-tex", BucketCategory::TEX, BucketId::PRIS_TEX_LEVEL1); // 51 - init_bucket_renderer("l1-pris-merc", BucketCategory::MERC, - BucketId::MERC_PRIS_LEVEL1); // 52 + init_merc_renderer("l1-pris-merc", BucketId::MERC_PRIS_LEVEL1); // 52 init_bucket_renderer("l1-pris-generic", BucketCategory::GENERIC, BucketId::GENERIC_PRIS_LEVEL1); // 53 // other renderers may output to the eye renderer m_render_state.eye_renderer = init_bucket_renderer( "common-pris-eyes", BucketCategory::OTHER, BucketId::MERC_EYES_AFTER_PRIS); // 54 - init_bucket_renderer("common-pris-merc", BucketCategory::MERC, - BucketId::MERC_AFTER_PRIS); // 55 + + // hack: set to merc2 for debugging + init_merc_renderer("common-pris-merc", BucketId::MERC_AFTER_PRIS); // 55 init_bucket_renderer("common-pris-generic", BucketCategory::GENERIC, BucketId::GENERIC_PRIS); // 56 @@ -234,8 +240,7 @@ void OpenGLRenderer::init_bucket_renderers() { //----------------------- init_bucket_renderer("l0-water-tex", BucketCategory::TEX, BucketId::WATER_TEX_LEVEL0); // 57 - init_bucket_renderer("l0-water-merc", BucketCategory::MERC, - BucketId::MERC_WATER_LEVEL0); // 58 + init_merc_renderer("l0-water-merc", BucketId::MERC_WATER_LEVEL0); // 58 init_bucket_renderer("l0-water-generic", BucketCategory::GENERIC, BucketId::GENERIC_WATER_LEVEL0); // 59 @@ -244,8 +249,7 @@ void OpenGLRenderer::init_bucket_renderers() { //----------------------- init_bucket_renderer("l1-water-tex", BucketCategory::TEX, BucketId::WATER_TEX_LEVEL1); // 60 - init_bucket_renderer("l1-water-merc", BucketCategory::MERC, - BucketId::MERC_WATER_LEVEL1); // 61 + init_merc_renderer("l1-water-merc", BucketId::MERC_WATER_LEVEL1); // 61 init_bucket_renderer("l1-water-generic", BucketCategory::GENERIC, BucketId::GENERIC_WATER_LEVEL1); // 62 @@ -295,6 +299,11 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { m_render_state.ee_main_memory = g_ee_main_mem; m_render_state.offset_of_s7 = offset_of_s7(); + // hack to toggle all mercs, todo remove once we finish merc2 + for (auto merc : m_mercs) { + merc->set_idx(m_render_state.merc2 ? 1 : 0); + } + { auto prof = m_profiler.root()->make_scoped_child("frame-setup"); setup_frame(settings.window_width_px, settings.window_height_px, settings.lbox_width_px, @@ -365,6 +374,7 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { void OpenGLRenderer::draw_renderer_selection_window() { ImGui::Begin("Renderer Debug"); + ImGui::Checkbox("Merc2", &m_render_state.merc2); ImGui::Checkbox("Use old single-draw", &m_render_state.no_multidraw); ImGui::SliderFloat("Fog Adjust", &m_render_state.fog_intensity, 0, 10); ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu); diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index b73c286223..8ec1e2f817 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -48,6 +48,8 @@ class OpenGLRenderer { void draw_renderer_selection_window(); void finish_screenshot(const std::string& output_name, int px, int py, int x, int y); + void init_merc_renderer(const std::string& name, BucketId id); + template T* init_bucket_renderer(const std::string& name, BucketCategory cat, @@ -67,6 +69,8 @@ class OpenGLRenderer { std::array, (int)BucketId::MAX_BUCKETS> m_bucket_renderers; std::array m_bucket_categories; + std::vector m_mercs; + std::array m_category_times; FullScreenDraw m_blackout_renderer; CollideMeshRenderer m_collide_renderer; diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index f92992d450..2c2fda7002 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -84,6 +84,7 @@ ShaderLibrary::ShaderLibrary() { at(ShaderId::SHRUB) = {"shrub"}; at(ShaderId::SHADOW) = {"shadow"}; at(ShaderId::COLLISION) = {"collision"}; + at(ShaderId::MERC2) = {"merc2"}; for (auto& shader : m_shaders) { ASSERT_MSG(shader.okay(), "Shader compiled"); diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index 622aae73fb..ec50baf37a 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -41,6 +41,7 @@ enum class ShaderId { SHADOW = 16, SHRUB = 17, COLLISION = 18, + MERC2 = 19, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/background/TFragment.cpp b/game/graphics/opengl_renderer/background/TFragment.cpp index 20273b5717..6bb39bdac0 100644 --- a/game/graphics/opengl_renderer/background/TFragment.cpp +++ b/game/graphics/opengl_renderer/background/TFragment.cpp @@ -31,10 +31,6 @@ TFragment::TFragment(const std::string& name, } } -constexpr const char* level_names[] = {"bea", "cit", "dar", "fin", "int", "jub", "jun", "fic", - "lav", "mai", "mis", "ogr", "rob", "rol", "sno", "sub", - "sun", "swa", "tit", "tra", "vi1", "vi2", "vi3"}; - void TFragment::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { @@ -150,43 +146,6 @@ void TFragment::render(DmaFollower& dma, auto tag = dma.current_tag().print(); dma.read_and_advance(); } - - if (m_hack_test_many_levels) { - std::vector all_kinds = { - tfrag3::TFragmentTreeKind::NORMAL, tfrag3::TFragmentTreeKind::TRANS, - tfrag3::TFragmentTreeKind::DIRT, tfrag3::TFragmentTreeKind::ICE, - tfrag3::TFragmentTreeKind::LOWRES, tfrag3::TFragmentTreeKind::LOWRES_TRANS}; - for (int i = 0; i < HackManyLevels::NUM_LEVELS; i++) { - if (m_many_level_render.level_enables[i]) { - if (!m_many_level_render.tfrag_level_renderers[i]) { - m_many_level_render.tfrag_level_renderers[i] = std::make_unique(); - } - if (!m_many_level_render.tie_level_renderers[i]) { - m_many_level_render.tie_level_renderers[i] = std::make_unique("tie", m_my_id, 0); - } - m_many_level_render.tfrag_level_renderers[i]->setup_for_level(all_kinds, level_names[i], - render_state); - m_many_level_render.tie_level_renderers[i]->setup_for_level(level_names[i], render_state); - TfragRenderSettings settings; - settings.hvdf_offset = m_tfrag_data.hvdf_offset; - settings.fog = m_tfrag_data.fog; - memcpy(settings.math_camera.data(), - &m_buffered_data[0].pad[TFragDataMem::TFragMatrix0 * 16], 64); - settings.tree_idx = 0; - for (int j = 0; j < 8; j++) { - settings.time_of_day_weights[j] = m_time_of_days[j]; - } - - auto t3prof = prof.make_scoped_child(level_names[i]); - - m_many_level_render.tfrag_level_renderers[i]->debug_render_all_trees_nolores( - 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); - } - } - } } void TFragment::draw_debug_window() { @@ -197,13 +156,6 @@ void TFragment::draw_debug_window() { } } - ImGui::Checkbox("Hack Test Many (danger)", &m_hack_test_many_levels); - if (m_hack_test_many_levels) { - for (int i = 0; i < HackManyLevels::NUM_LEVELS; i++) { - ImGui::Checkbox(level_names[i], &m_many_level_render.level_enables[i]); - } - } - m_tfrag3.draw_debug_window(); } diff --git a/game/graphics/opengl_renderer/background/TFragment.h b/game/graphics/opengl_renderer/background/TFragment.h index c079a00555..7b92cc784a 100644 --- a/game/graphics/opengl_renderer/background/TFragment.h +++ b/game/graphics/opengl_renderer/background/TFragment.h @@ -47,7 +47,6 @@ class TFragment : public BucketRenderer { void handle_initialization(DmaFollower& dma); bool m_child_mode = false; - bool m_hack_test_many_levels = false; bool m_override_time_of_day = false; float m_time_of_days[8] = {1, 0, 0, 0, 0, 0, 0, 0}; @@ -80,11 +79,4 @@ class TFragment : public BucketRenderer { Tfrag3 m_tfrag3; std::vector m_tree_kinds; int m_level_id; - - struct HackManyLevels { - static constexpr int NUM_LEVELS = 23; - std::unique_ptr tfrag_level_renderers[NUM_LEVELS]; - std::unique_ptr tie_level_renderers[NUM_LEVELS]; - bool level_enables[NUM_LEVELS] = {0}; - } m_many_level_render; }; diff --git a/game/graphics/opengl_renderer/background/Tfrag3.cpp b/game/graphics/opengl_renderer/background/Tfrag3.cpp index 25fc0cf4db..70bfeb8017 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/background/Tfrag3.cpp @@ -317,33 +317,9 @@ void Tfrag3::render_matching_trees(int geom, } } -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[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(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); - // } - // } -} - void Tfrag3::draw_debug_window() { - for (int i = 0; i < (int)m_cached_trees.size(); i++) { - auto& tree = m_cached_trees[lod()][i]; + for (int i = 0; i < (int)m_cached_trees.at(lod()).size(); i++) { + auto& tree = m_cached_trees.at(lod()).at(i); if (tree.kind == tfrag3::TFragmentTreeKind::INVALID) { continue; } diff --git a/game/graphics/opengl_renderer/background/Tfrag3.h b/game/graphics/opengl_renderer/background/Tfrag3.h index c259342f8f..9bec90e393 100644 --- a/game/graphics/opengl_renderer/background/Tfrag3.h +++ b/game/graphics/opengl_renderer/background/Tfrag3.h @@ -12,11 +12,6 @@ class Tfrag3 { Tfrag3(); ~Tfrag3(); - void debug_render_all_trees_nolores(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - void render_all_trees(int geom, const TfragRenderSettings& settings, SharedRenderState* render_state, @@ -57,7 +52,7 @@ class Tfrag3 { static constexpr int GEOM_MAX = 3; struct TreeCache { - tfrag3::TFragmentTreeKind kind; + tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID; GLuint vertex_buffer = -1; GLuint index_buffer = -1; GLuint single_draw_index_buffer = -1; diff --git a/game/graphics/opengl_renderer/foreground/Generic2.h b/game/graphics/opengl_renderer/foreground/Generic2.h index 83ada8f931..244f062995 100644 --- a/game/graphics/opengl_renderer/foreground/Generic2.h +++ b/game/graphics/opengl_renderer/foreground/Generic2.h @@ -14,7 +14,6 @@ class Generic2 : public BucketRenderer { void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; void init_shaders(ShaderLibrary& shaders) override; - // void init_shaders(ShaderLibrary& shaders) override; struct Vertex { math::Vector xyz; diff --git a/game/graphics/opengl_renderer/foreground/Merc2.cpp b/game/graphics/opengl_renderer/foreground/Merc2.cpp new file mode 100644 index 0000000000..5796d60504 --- /dev/null +++ b/game/graphics/opengl_renderer/foreground/Merc2.cpp @@ -0,0 +1,604 @@ +#include "Merc2.h" +#include "third-party/imgui/imgui.h" +#include "game/graphics/opengl_renderer/background/background_common.h" + +Merc2::Merc2(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) { + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + + glGenBuffers(1, &m_bones_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer); + glBufferData(GL_UNIFORM_BUFFER, MAX_SHADER_BONES * sizeof(MercMat), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + for (int i = 0; i < MAX_LEVELS; i++) { + auto& draws = m_level_draw_buckets.emplace_back(); + draws.draws.resize(MAX_DRAWS_PER_LEVEL); + } +} + +/*! + * Handle the merc renderer switching to a different model. + */ +void Merc2::init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state) { + // determine the name. We've packed this in a separate PC-port specific packet. + char name[128]; + strcpy(name, (const char*)setup.data); + + // get the model from the loader + m_current_model = render_state->loader->get_merc_model(name); + + // update stats + m_stats.num_models++; + if (m_current_model) { + for (const auto& effect : m_current_model->model->effects) { + m_stats.num_effects++; + m_stats.num_predicted_draws += effect.draws.size(); + for (const auto& draw : effect.draws) { + m_stats.num_predicted_tris += draw.num_triangles; + } + } + } +} + +/*! + * Once-per-frame initialization + */ +void Merc2::init_for_frame(SharedRenderState* render_state) { + // reset state + m_current_model = std::nullopt; + m_stats = {}; + + // activate the merc shader used for all draws + render_state->shaders[ShaderId::MERC2].activate(); + + // set uniforms that we know from render_state + glUniform4f(m_uniforms.fog_color, render_state->fog_color[0], render_state->fog_color[1], + render_state->fog_color[2], render_state->fog_intensity); +} + +void Merc2::draw_debug_window() { + ImGui::Text("Models : %d", m_stats.num_models); + ImGui::Text("Effects : %d", m_stats.num_effects); + ImGui::Text("Draws (p): %d", m_stats.num_predicted_draws); + ImGui::Text("Tris (p): %d", m_stats.num_predicted_tris); + ImGui::Text("Bones : %d", m_stats.num_bones_uploaded); + ImGui::Text("Lights : %d", m_stats.num_lights); + ImGui::Text("Dflush : %d", m_stats.num_draw_flush); +} + +void Merc2::init_shaders(ShaderLibrary& shaders) { + shaders[ShaderId::MERC2].activate(); + m_uniforms.light_direction[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir0"); + m_uniforms.light_direction[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir1"); + m_uniforms.light_direction[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir2"); + m_uniforms.light_color[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col0"); + m_uniforms.light_color[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col1"); + m_uniforms.light_color[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col2"); + m_uniforms.light_ambient = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_ambient"); + + m_uniforms.hvdf_offset = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "hvdf_offset"); + m_uniforms.perspective[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective0"); + m_uniforms.perspective[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective1"); + m_uniforms.perspective[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective2"); + m_uniforms.perspective[3] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective3"); + + m_uniforms.fog = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_constants"); + m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "decal_enable"); + + m_uniforms.fog_color = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_color"); + m_uniforms.perspective_matrix = + glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective_matrix"); + m_uniforms.ignore_alpha = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "ignore_alpha"); +} + +/*! + * Main merc2 rendering. + */ +void Merc2::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) { + // skip if disabled + if (!m_enabled) { + while (dma.current_tag_offset() != render_state->next_bucket) { + dma.read_and_advance(); + } + return; + } + + init_for_frame(render_state); + + // iterate through the dma chain, filling buckets + handle_all_dma(dma, render_state, prof); + + // flush model data to buckets + flush_pending_model(render_state, prof); + // flush buckets to draws + flush_draw_buckets(render_state, prof); +} + +/*! + * Queue up some bones to be included in the bone buffer. + * Returns the index of the first bone. + */ +u32 Merc2::alloc_bones(int count, float scale) { + ASSERT(count + m_next_free_bone <= MAX_SHADER_BONES); + + u32 first_bone = m_next_free_bone; + + // model should have under 128 bones. + ASSERT(count <= MAX_SKEL_BONES); + + // iterate over each bone we need + for (int i = 0; i < count; i++) { + auto& skel_mat = m_skel_matrix_buffer[i]; + auto& shader_mat = m_shader_matrix_buffer[m_next_free_bone++]; + + // scale the transformation matrix (todo: can we move this to the extraction) + // and copy to the large bone buffer. + for (int j = 0; j < 3; j++) { + shader_mat.tmat[j] = skel_mat.tmat[j] * scale; + } + shader_mat.tmat[3] = skel_mat.tmat[3]; + + for (int j = 0; j < 3; j++) { + shader_mat.nmat[j] = skel_mat.nmat[j]; + } + + // we could include the effect of the perspective matrix here. + // for (int j = 0; j < 3; j++) { + // tbone_buffer[i][j] = vf15.elementwise_multiply(bone_mat[j]); + // tbone_buffer[i][j].w() += p.w() * bone_mat[j].z(); + // tbone_buffer[i][j] *= scale; + // } + // + // tbone_buffer[i][3] = vf15.elementwise_multiply(bone_mat[3]) + + // m_low_memory.perspective[3]; tbone_buffer[i][3].w() += p.w() * bone_mat[3].z(); + } + + return first_bone; +} + +u32 Merc2::alloc_lights(const VuLights& lights) { + ASSERT(m_next_free_light < MAX_LIGHTS); + m_stats.num_lights++; + u32 light_idx = m_next_free_light; + m_lights_buffer[m_next_free_light++] = lights; + static_assert(sizeof(VuLights) == 7 * 16); + return light_idx; +} + +/*! + * Store light values + */ +void Merc2::set_lights(const DmaTransfer& dma) { + memcpy(&m_current_lights, dma.data, sizeof(VuLights)); +} + +void Merc2::handle_matrix_dma(const DmaTransfer& dma) { + int slot = dma.vif0() & 0xff; + ASSERT(slot < MAX_SKEL_BONES); + memcpy(&m_skel_matrix_buffer[slot], dma.data, sizeof(MercMat)); +} + +/*! + * Main MERC2 function to handle DMA + */ +void Merc2::handle_all_dma(DmaFollower& dma, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + // process the first tag. this is just jumping to the merc-specific dma. + auto data0 = dma.read_and_advance(); + ASSERT(data0.vif1() == 0); + ASSERT(data0.vif0() == 0); + ASSERT(data0.size_bytes == 0); + if (dma.current_tag().kind == DmaTag::Kind::CALL) { + // renderer didn't run, let's just get out of here. + for (int i = 0; i < 4; i++) { + dma.read_and_advance(); + } + ASSERT(dma.current_tag_offset() == render_state->next_bucket); + return; + } + ASSERT(data0.size_bytes == 0); + ASSERT(data0.vif0() == 0); + ASSERT(data0.vif1() == 0); + + // if we reach here, there's stuff to draw + // this handles merc-specific setup DMA + handle_setup_dma(dma); + + // handle each merc transfer + while (dma.current_tag_offset() != render_state->next_bucket) { + handle_merc_chain(dma, render_state, prof); + } + ASSERT(dma.current_tag_offset() == render_state->next_bucket); +} + +namespace { +void set_uniform(GLuint uniform, const math::Vector3f& val) { + glUniform3f(uniform, val.x(), val.y(), val.z()); +} +void set_uniform(GLuint uniform, const math::Vector4f& val) { + glUniform4f(uniform, val.x(), val.y(), val.z(), val.w()); +} +} // namespace + +void Merc2::handle_setup_dma(DmaFollower& dma) { + auto first = dma.read_and_advance(); + + // 10 quadword setup packet + ASSERT(first.size_bytes == 10 * 16); + // m_stats.str += fmt::format("Setup 0: {} {} {}", first.size_bytes / 16, + // first.vifcode0().print(), first.vifcode1().print()); + + // transferred vifcodes + { + auto vif0 = first.vifcode0(); + auto vif1 = first.vifcode1(); + // STCYCL 4, 4 + ASSERT(vif0.kind == VifCode::Kind::STCYCL); + auto vif0_st = VifCodeStcycl(vif0); + ASSERT(vif0_st.cl == 4 && vif0_st.wl == 4); + // STMOD + ASSERT(vif1.kind == VifCode::Kind::STMOD); + ASSERT(vif1.immediate == 0); + } + + // 1 qw with 4 vifcodes. + u32 vifcode_data[4]; + memcpy(vifcode_data, first.data, 16); + { + auto vif0 = VifCode(vifcode_data[0]); + ASSERT(vif0.kind == VifCode::Kind::BASE); + ASSERT(vif0.immediate == MercDataMemory::BUFFER_BASE); + auto vif1 = VifCode(vifcode_data[1]); + ASSERT(vif1.kind == VifCode::Kind::OFFSET); + ASSERT((s16)vif1.immediate == MercDataMemory::BUFFER_OFFSET); + auto vif2 = VifCode(vifcode_data[2]); + ASSERT(vif2.kind == VifCode::Kind::NOP); + auto vif3 = VifCode(vifcode_data[3]); + ASSERT(vif3.kind == VifCode::Kind::UNPACK_V4_32); + VifCodeUnpack up(vif3); + ASSERT(up.addr_qw == MercDataMemory::LOW_MEMORY); + ASSERT(!up.use_tops_flag); + ASSERT(vif3.num == 8); + } + + // 8 qw's of low memory data + memcpy(&m_low_memory, first.data + 16, sizeof(LowMemory)); + set_uniform(m_uniforms.hvdf_offset, m_low_memory.hvdf_offset); + set_uniform(m_uniforms.fog, m_low_memory.fog); + for (int i = 0; i < 4; i++) { + set_uniform(m_uniforms.perspective[i], m_low_memory.perspective[i]); + } + // todo rm. + glUniformMatrix4fv(m_uniforms.perspective_matrix, 1, GL_FALSE, &m_low_memory.perspective[0].x()); + + // 1 qw with another 4 vifcodes. + u32 vifcode_final_data[4]; + memcpy(vifcode_final_data, first.data + 16 + sizeof(LowMemory), 16); + { + ASSERT(VifCode(vifcode_final_data[0]).kind == VifCode::Kind::FLUSHE); + ASSERT(vifcode_final_data[1] == 0); + ASSERT(vifcode_final_data[2] == 0); + VifCode mscal(vifcode_final_data[3]); + ASSERT(mscal.kind == VifCode::Kind::MSCAL); + ASSERT(mscal.immediate == 0); + } + + // TODO: process low memory initialization + + auto second = dma.read_and_advance(); + ASSERT(second.size_bytes == 32); // setting up test register. + auto nothing = dma.read_and_advance(); + ASSERT(nothing.size_bytes == 0); + ASSERT(nothing.vif0() == 0); + ASSERT(nothing.vif1() == 0); +} + +namespace { +bool tag_is_nothing_next(const DmaFollower& dma) { + return dma.current_tag().kind == DmaTag::Kind::NEXT && dma.current_tag().qwc == 0 && + dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0; +} +bool tag_is_nothing_cnt(const DmaFollower& dma) { + return dma.current_tag().kind == DmaTag::Kind::CNT && dma.current_tag().qwc == 0 && + dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0; +} +} // namespace + +void Merc2::handle_merc_chain(DmaFollower& dma, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + while (tag_is_nothing_next(dma)) { + auto nothing = dma.read_and_advance(); + ASSERT(nothing.size_bytes == 0); + } + if (dma.current_tag().kind == DmaTag::Kind::CALL) { + for (int i = 0; i < 4; i++) { + dma.read_and_advance(); + } + return; + } + + auto init = dma.read_and_advance(); + + if (init.vifcode1().kind == VifCode::Kind::PC_PORT) { + // we got a PC PORT packet. this contains some extra data to set up the model + flush_pending_model(render_state, prof); + init_pc_model(init, render_state); + ASSERT(tag_is_nothing_cnt(dma)); + init = dma.read_and_advance(); // dummy tag in pc port + init = dma.read_and_advance(); + } + + // row stuff. + ASSERT(init.vifcode0().kind == VifCode::Kind::STROW); + ASSERT(init.size_bytes == 16); + // m_vif.row[0] = init.vif1(); + // memcpy(m_vif.row + 1, init.data, 12); + u32 extra; + memcpy(&extra, init.data + 12, 4); + // ASSERT(extra == 0); + m_current_effect_enable_bits = extra; + m_current_ignore_alpha_bits = extra >> 16; + DmaTransfer next; + + bool setting_up = true; + u32 mscal_addr = -1; + while (setting_up) { + next = dma.read_and_advance(); + // fmt::print("next: {}", dma.current_tag().print()); + u32 offset_in_data = 0; + // fmt::print("START {} : {} {}\n", next.size_bytes, next.vifcode0().print(), + // next.vifcode1().print()); + auto vif0 = next.vifcode0(); + switch (vif0.kind) { + case VifCode::Kind::NOP: + case VifCode::Kind::FLUSHE: + break; + case VifCode::Kind::STMOD: + ASSERT(vif0.immediate == 0 || vif0.immediate == 1); + // m_vif.stmod = vif0.immediate; + break; + default: + ASSERT(false); + } + + auto vif1 = next.vifcode1(); + switch (vif1.kind) { + case VifCode::Kind::UNPACK_V4_8: { + VifCodeUnpack up(vif1); + offset_in_data += 4 * vif1.num; + } break; + case VifCode::Kind::UNPACK_V4_32: { + VifCodeUnpack up(vif1); + if (up.addr_qw == 132 && vif1.num == 8) { + set_lights(next); + } else if (vif1.num == 7) { + handle_matrix_dma(next); + } + offset_in_data += 16 * vif1.num; + } break; + case VifCode::Kind::MSCAL: + // fmt::print("cal\n"); + mscal_addr = vif1.immediate; + ASSERT(next.size_bytes == 0); + setting_up = false; + break; + default: + ASSERT(false); + } + + ASSERT(offset_in_data <= next.size_bytes); + if (offset_in_data < next.size_bytes) { + ASSERT((offset_in_data % 4) == 0); + u32 leftover = next.size_bytes - offset_in_data; + if (leftover < 16) { + for (u32 i = 0; i < leftover; i++) { + ASSERT(next.data[offset_in_data + i] == 0); + } + } else { + ASSERT(false); + } + } + } +} + +/*! + * Flush a model to draw buckets + */ +void Merc2::flush_pending_model(SharedRenderState* render_state, ScopedProfilerNode& prof) { + if (!m_current_model) { + return; + } + + const Loader::LevelData* lev = m_current_model->level; + const tfrag3::MercModel* model = m_current_model->model; + + int bone_count = (model->max_bones + 31) & (~31); // todo + // int bone_count = 128; + + if (m_next_free_light >= MAX_LIGHTS) { + fmt::print("MERC2 out of lights, consider increasing MAX_LIGHTS\n"); + flush_draw_buckets(render_state, prof); + } + + if (m_next_free_bone + bone_count > MAX_SHADER_BONES) { + fmt::print("MERC2 out of bones, consider increasing MAX_SHADER_BONES\n"); + flush_draw_buckets(render_state, prof); + } + + // find a level bucket + LevelDrawBucket* lev_bucket = nullptr; + for (u32 i = 0; i < m_next_free_level_bucket; i++) { + if (m_level_draw_buckets[i].level == lev) { + lev_bucket = &m_level_draw_buckets[i]; + break; + } + } + + if (!lev_bucket) { + // no existing bucket + if (m_next_free_level_bucket >= m_level_draw_buckets.size()) { + // out of room, flush + fmt::print("MERC2 out of levels, consider increasing MAX_LEVELS\n"); + flush_draw_buckets(render_state, prof); + // and retry the whole thing. + flush_pending_model(render_state, prof); + return; + } + // alloc a new one + lev_bucket = &m_level_draw_buckets[m_next_free_level_bucket++]; + lev_bucket->reset(); + lev_bucket->level = lev; + } + + if (lev_bucket->next_free_draw + model->max_draws >= lev_bucket->draws.size()) { + // out of room, flush + fmt::print("MERC2 out of draws, consider increasing MAX_DRAWS_PER_LEVEL\n"); + flush_draw_buckets(render_state, prof); + // and retry the whole thing. + flush_pending_model(render_state, prof); + return; + } + + u32 first_bone = alloc_bones(bone_count, model->scale_xyz); + + // allocate lights + u32 lights = alloc_lights(m_current_lights); + // + for (size_t ei = 0; ei < model->effects.size(); ei++) { + if (!(m_current_effect_enable_bits & (1 << ei))) { + continue; + } + + u8 ignore_alpha = (m_current_ignore_alpha_bits & (1 << ei)); + auto& effect = model->effects[ei]; + for (auto& mdraw : effect.draws) { + Draw* draw = &lev_bucket->draws[lev_bucket->next_free_draw++]; + draw->first_index = mdraw.first_index; + draw->index_count = mdraw.index_count; + draw->mode = mdraw.mode; + draw->texture = mdraw.tree_tex_id; + draw->first_bone = first_bone; + draw->light_idx = lights; + draw->num_triangles = mdraw.num_triangles; + draw->ignore_alpha = ignore_alpha; + } + } + + m_current_model = std::nullopt; +} + +void Merc2::flush_draw_buckets(SharedRenderState* /*render_state*/, ScopedProfilerNode& prof) { + m_stats.num_draw_flush++; + + for (u32 li = 0; li < m_next_free_level_bucket; li++) { + const auto& lev_bucket = m_level_draw_buckets[li]; + const auto* lev = lev_bucket.level; + glBindVertexArray(m_vao); + glBindBuffer(GL_ARRAY_BUFFER, lev->merc_vertices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lev->merc_indices); + + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(UINT32_MAX); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_GEQUAL); + + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::MercVertex), // stride + (void*)offsetof(tfrag3::MercVertex, pos) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::MercVertex), // stride + (void*)offsetof(tfrag3::MercVertex, normal[0]) // offset (0) + ); + + glVertexAttribPointer(2, // location 1 in the + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::MercVertex), // stride + (void*)offsetof(tfrag3::MercVertex, weights[0]) // offset (0) + ); + + glVertexAttribPointer(3, // location 1 in the shader + 2, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::MercVertex), // stride + (void*)offsetof(tfrag3::MercVertex, st[0]) // offset (0) + ); + + glVertexAttribPointer(4, // location 1 in the shader + 3, // 3 values per vert + GL_UNSIGNED_BYTE, // floats + GL_TRUE, // normalized + sizeof(tfrag3::MercVertex), // stride + (void*)offsetof(tfrag3::MercVertex, rgba[0]) // offset (0) + ); + + glVertexAttribIPointer(5, // location 0 in the + 3, // 3 floats per vert + GL_UNSIGNED_BYTE, // u8's + sizeof(tfrag3::MercVertex), // + (void*)offsetof(tfrag3::MercVertex, mats[0]) // offset in array + ); + + int last_tex = -1; + int last_light = -1; + m_stats.num_bones_uploaded += m_next_free_bone; + + glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer); + glBufferSubData(GL_UNIFORM_BUFFER, 0, m_next_free_bone * sizeof(MercMat), + m_shader_matrix_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + for (u32 di = 0; di < lev_bucket.next_free_draw; di++) { + auto& draw = lev_bucket.draws[di]; + glUniform1i(m_uniforms.ignore_alpha, draw.ignore_alpha); + if ((int)draw.texture != last_tex) { + glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture)); + last_tex = draw.texture; + } + + if ((int)draw.light_idx != last_light) { + set_uniform(m_uniforms.light_direction[0], m_lights_buffer[draw.light_idx].direction0); + set_uniform(m_uniforms.light_direction[1], m_lights_buffer[draw.light_idx].direction1); + set_uniform(m_uniforms.light_direction[2], m_lights_buffer[draw.light_idx].direction2); + set_uniform(m_uniforms.light_color[0], m_lights_buffer[draw.light_idx].color0); + set_uniform(m_uniforms.light_color[1], m_lights_buffer[draw.light_idx].color1); + set_uniform(m_uniforms.light_color[2], m_lights_buffer[draw.light_idx].color2); + set_uniform(m_uniforms.light_ambient, m_lights_buffer[draw.light_idx].ambient); + last_light = draw.light_idx; + } + setup_opengl_from_draw_mode(draw.mode, GL_TEXTURE0, true); + + glUniform1i(m_uniforms.decal, draw.mode.get_decal()); + + prof.add_draw_call(); + prof.add_tri(draw.num_triangles); + glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_bones_buffer, sizeof(MercMat) * draw.first_bone, + 128 * sizeof(MercMat)); + glDrawElements(GL_TRIANGLE_STRIP, draw.index_count, GL_UNSIGNED_INT, + (void*)(sizeof(u32) * draw.first_index)); + } + } + + m_next_free_light = 0; + m_next_free_bone = 0; + m_next_free_level_bucket = 0; +} diff --git a/game/graphics/opengl_renderer/foreground/Merc2.h b/game/graphics/opengl_renderer/foreground/Merc2.h new file mode 100644 index 0000000000..17472d2d9b --- /dev/null +++ b/game/graphics/opengl_renderer/foreground/Merc2.h @@ -0,0 +1,143 @@ +#pragma once +#include "game/graphics/opengl_renderer/BucketRenderer.h" + +class Merc2 : public BucketRenderer { + public: + Merc2(const std::string& name, BucketId my_id); + void draw_debug_window() override; + void init_shaders(ShaderLibrary& shaders) override; + void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; + void handle_merc_chain(DmaFollower& dma, + SharedRenderState* render_state, + ScopedProfilerNode& prof); + + private: + enum MercDataMemory { + LOW_MEMORY = 0, + BUFFER_BASE = 442, + // this negative offset is what broke jak graphics in Dobiestation for a long time. + BUFFER_OFFSET = -442 + }; + + struct LowMemory { + u8 tri_strip_tag[16]; + u8 ad_gif_tag[16]; + math::Vector4f hvdf_offset; + math::Vector4f perspective[4]; + math::Vector4f fog; + } m_low_memory; + static_assert(sizeof(LowMemory) == 0x80); + + struct VuLights { + math::Vector3f direction0; + u32 w0; + math::Vector3f direction1; + u32 w1; + math::Vector3f direction2; + u32 w2; + math::Vector3f color0; + u32 w3; + math::Vector3f color1; + u32 w4; + math::Vector3f color2; + u32 w5; + math::Vector3f ambient; + u32 w6; + }; + + void init_for_frame(SharedRenderState* render_state); + void init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state); + void handle_all_dma(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof); + void handle_setup_dma(DmaFollower& dma); + u32 alloc_lights(const VuLights& lights); + void set_lights(const DmaTransfer& dma); + void handle_matrix_dma(const DmaTransfer& dma); + void flush_pending_model(SharedRenderState* render_state, ScopedProfilerNode& prof); + + u32 alloc_bones(int count, float scale); + + std::optional m_current_model = std::nullopt; + u16 m_current_effect_enable_bits = 0; + u16 m_current_ignore_alpha_bits = 0; + + struct MercMat { + math::Vector4f tmat[4]; + math::Vector4f nmat[3]; + }; + + static constexpr int MAX_SKEL_BONES = 128; + static constexpr int MAX_SHADER_BONES = 8192; + + static constexpr int MAX_LEVELS = 3; + static constexpr int MAX_DRAWS_PER_LEVEL = 1024; + + MercMat m_shader_matrix_buffer[MAX_SHADER_BONES]; + MercMat m_skel_matrix_buffer[MAX_SKEL_BONES]; + + struct { + GLuint light_direction[3]; + GLuint light_color[3]; + GLuint light_ambient; + + GLuint hvdf_offset; + GLuint perspective[4]; + GLuint fog; + + GLuint tbone; + GLuint nbone; + + GLuint fog_color; + GLuint perspective_matrix; + + GLuint ignore_alpha; + GLuint decal; + } m_uniforms; + + GLuint m_vao; + + GLuint m_bones_buffer; + + struct Stats { + int num_models = 0; + int num_chains = 0; + int num_effects = 0; + int num_predicted_draws = 0; + int num_predicted_tris = 0; + int num_bones_uploaded = 0; + int num_lights = 0; + int num_draw_flush = 0; + } m_stats; + + struct Draw { + u32 first_index; + u32 index_count; + DrawMode mode; + u32 texture; + u32 num_triangles; + u16 first_bone; + u16 light_idx; + u8 ignore_alpha; + }; + + struct LevelDrawBucket { + const Loader::LevelData* level = nullptr; + std::vector draws; + u32 next_free_draw = 0; + + void reset() { + level = nullptr; + next_free_draw = 0; + } + }; + + static constexpr int MAX_LIGHTS = 256; + VuLights m_lights_buffer[MAX_LIGHTS]; + u32 m_next_free_light = 0; + VuLights m_current_lights; + + std::vector m_level_draw_buckets; + u32 m_next_free_level_bucket = 0; + u32 m_next_free_bone = 0; + + void flush_draw_buckets(SharedRenderState* render_state, ScopedProfilerNode& prof); +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/merc2.frag b/game/graphics/opengl_renderer/shaders/merc2.frag new file mode 100644 index 0000000000..5fd680f2d7 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/merc2.frag @@ -0,0 +1,34 @@ +#version 430 core + +out vec4 color; +in vec3 vtx_color; +in vec2 vtx_st; +in float fog; + + +uniform sampler2D tex_T0; + +uniform vec4 fog_color; +uniform int ignore_alpha; + +uniform int decal_enable; + + +void main() { + vec4 T0 = texture(tex_T0, vtx_st); + + if (decal_enable == 0) { + color.xyz = vtx_color * T0.xyz; + } else { + color.xyz = T0.xyz * 0.5; + } + color.w = T0.w; + color *= 2; + + + if (ignore_alpha == 0 && color.w < 0.128) { + discard; + } + + color.xyz = mix(color.xyz, fog_color.xyz / 255., clamp(fog, 0, 1)); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/merc2.vert b/game/graphics/opengl_renderer/shaders/merc2.vert new file mode 100644 index 0000000000..7393250a6f --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/merc2.vert @@ -0,0 +1,115 @@ +#version 430 core + +// merc vertex definition +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec3 normal_in; +layout (location = 2) in vec3 weights_in; +layout (location = 3) in vec2 st_in; +layout (location = 4) in vec3 rgba; +layout (location = 5) in uvec3 mats; + +// light control +uniform vec3 light_dir0; +uniform vec3 light_dir1; +uniform vec3 light_dir2; +uniform vec3 light_col0; +uniform vec3 light_col1; +uniform vec3 light_col2; +uniform vec3 light_ambient; + +// camera control +uniform vec4 hvdf_offset; +uniform vec4 perspective0; +uniform vec4 perspective1; +uniform vec4 perspective2; +uniform vec4 perspective3; +uniform vec4 fog_constants; + +uniform mat4 perspective_matrix; + +// output +out vec3 vtx_color; +out vec2 vtx_st; + +out float fog; + +struct MercMatrixData { + mat4 X; + mat3 R; +}; + +layout (std140, binding = 1) uniform ub_bones { + MercMatrixData bones[128]; +}; + + +/* +The inputs are in registers 8, 10, 12, 25, and the outputs are 9, 11, 13, 26. The output is written over the input. + +``` +mula.xyzw ACC, vf15, vf08 +maddz.xyzw vf09, vf16, vf08 +mula.xyzw ACC, vf15, vf10 +maddz.xyzw vf11, vf16, vf10 +mula.xyzw ACC, vf15, vf12 +maddz.xyzw vf13, vf16, vf12 +addax.xyzw vf20, vf00 +madda.xyzw ACC, vf27, vf25 +maddz.xyzw vf26, vf28, vf25 +``` +*/ +void main() { + // vec4 transformed = -perspective3.xyzw; + // transformed += -perspective0 * position_in.x; + // transformed += -perspective1 * position_in.y; + // transformed += -perspective2 * position_in.z; + + +// vec4 transformed = -hmat3.xyzw; +// transformed += -hmat0 * position_in.x; +// transformed += -hmat1 * position_in.y; +// transformed += -hmat2 * position_in.z; + + vec4 p = vec4(position_in, 1); + vec4 vtx_pos = -bones[mats[0]].X * p * weights_in[0] + -bones[mats[1]].X * p * weights_in[1] + -bones[mats[2]].X * p * weights_in[2]; + + vec4 transformed = perspective_matrix * vtx_pos; + + vec3 rotated_nrm = bones[mats[0]].R * normal_in * weights_in[0] + + bones[mats[1]].R * normal_in * weights_in[1] + + bones[mats[2]].R * normal_in * weights_in[2]; + + rotated_nrm = normalize(rotated_nrm); + vec3 light_intensity = light_dir0 * rotated_nrm.x + light_dir1 * rotated_nrm.y + light_dir2 * rotated_nrm.z; + light_intensity = max(light_intensity, vec3(0, 0, 0)); + + vec3 light_color = light_ambient + + light_intensity.x * light_col0 + + light_intensity.y * light_col1 + + light_intensity.z * light_col2; + + + + float Q = fog_constants.x / transformed[3]; + float fog1 = -transformed.w + hvdf_offset.w; + float fog2 = min(fog1, fog_constants.z); + float fog3 = max(fog2, fog_constants.y); + fog = 1 - (fog3/256); + + transformed.xyz *= Q; + transformed.xyz += hvdf_offset.xyz; + transformed.xy -= (2048.); + transformed.z /= (8388608); + transformed.z -= 1; + transformed.x /= (256); + transformed.y /= -(128); + transformed.xyz *= transformed.w; + gl_Position = transformed; + gl_Position.y *= 512.0/448.0; + + + vtx_color = rgba * light_color; + vtx_st = st_in; +} diff --git a/game/kernel/klink.cpp b/game/kernel/klink.cpp index 38d0b1bcad..e787f380e7 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/klink.cpp @@ -9,6 +9,7 @@ #include #include #include +#include "common/log/log.h" #include "klink.h" #include "fileio.h" #include "kscheme.h" @@ -778,6 +779,7 @@ void link_control::finish(bool jump_from_c_to_goal) { *EnableMethodSet = *EnableMethodSet + m_keep_debug; ObjectFileHeader* ofh = m_link_block_ptr.cast().c(); + lg::info("link finish: {}", m_object_name); if (ofh->object_file_version == 3) { // todo check function type of entry diff --git a/game/mips2c/functions/bones.cpp b/game/mips2c/functions/bones.cpp index f4d1f7a9c9..5f29a66dd1 100644 --- a/game/mips2c/functions/bones.cpp +++ b/game/mips2c/functions/bones.cpp @@ -700,6 +700,51 @@ struct Cache { void* fake_scratchpad_data; } cache; +/* + (deftype merc-effect-bucket-info (structure) + ((color-fade rgba :offset-assert 0) + (use-mercneric uint8 :offset-assert 4) + (ignore-alpha uint8 :offset-assert 5) + (pad0 uint8 :offset-assert 6) + (pad1 uint8 :offset-assert 7) + ) + :pack-me + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) + +;; information for everything being submitted. +(deftype merc-bucket-info (structure) + ((light vu-lights :inline :offset-assert 0) + (needs-clip int32 :offset-assert 112) + (need-mercprime-if-merc int32 :offset-assert 116) + (must-use-mercneric-for-clip int32 :offset-assert 120) + (effect merc-effect-bucket-info 16 :inline :offset-assert 124) + ) + :method-count-assert 9 + :size-assert #xfc + :flag-assert #x9000000fc + ) + */ + +struct MercEffectBucketInfo { + u8 color_fade[4]; + u8 use_mercneric; + u8 ignore_alpha; + u8 pad0; + u8 pad1; +}; + +struct MercBucketInfo { + u8 lights[0x70]; + u32 needs_clip; + u32 mercprime; + u32 mercneric; + MercEffectBucketInfo effects[16]; +}; +static_assert(sizeof(MercBucketInfo) == 0xfc); + u64 execute(void* ctxt) { auto* c = (ExecutionContext*)ctxt; bool bc = false; @@ -752,6 +797,20 @@ u64 execute(void* ctxt) { c->pcpyld(t6, t9, t6); // pcpyld t6, t9, t6 c->daddiu(t9, t7, 108); // daddiu t9, t7, 108 // the actual merc-effect c->load_symbol(a3, cache.merc_bucket_info); // lw a3, *merc-bucket-info*(s7) + + // PC HACK: built a bitmask of which effects end up using mercneric. + const MercBucketInfo* mbi = (const MercBucketInfo*)(g_ee_main_mem + c->sgpr64(a3)); + u16 use_pc_merc_bits = 0; + u16 ignore_alpha_bits = 0; + for (int i = 0; i < 16; i++) { + if (!mbi->effects[i].use_mercneric) { + use_pc_merc_bits |= (1 << i); + } + if (mbi->effects[i].ignore_alpha) { + ignore_alpha_bits |= (1 << i); + } + } + c->daddiu(ra, a3, 124); // daddiu ra, a3, 124 // effect bucket infos // effect loop! @@ -821,6 +880,10 @@ block_3: c->sw(a3, 12, a2); // sw a3, 12(a2) // st-vif-add's x. c->srl(s0, s0, 2); // srl s0, s0, 2 // lump fours / 4 c->sq(t1, 16, a2); // sq t1, 16(a2) // row y (will be overwritten) z w (nop). + // PC HACK: sneak in the bits here: + memcpy(g_ee_main_mem + c->sgpr64(a2) + 28, &use_pc_merc_bits, 2); + memcpy(g_ee_main_mem + c->sgpr64(a2) + 30, &ignore_alpha_bits, 2); + // store the dma tag for the lump fours c->xor_(t3, t3, s0); // xor t3, t3, s0 @@ -898,6 +961,10 @@ block_3: c->sq(t5, 0, a2); // sq t5, 0(a2) // dma template c->lbu(s0, 1, gp); // lbu s0, 1(gp) // s0 = mat-dest c->daddiu(gp, gp, 2); // daddiu gp, gp, 2 // inc mat-dest-data pr + + // HACK for PC PORT: stash the source matrix number in the unused bits of nop viftag. + c->sb(a3, 8, a2); + c->lbu(a3, 0, gp); // lbu a3, 0(gp) // load for next iter (ugh) c->daddiu(s3, s3, -1); // daddiu s3, s3, -1 // dec count c->sb(s0, 12, a2); // sb s0, 12(a2) // store matrix destination. diff --git a/goal_src/engine/anim/bones.gc b/goal_src/engine/anim/bones.gc index 8418441690..c59b4ec113 100644 --- a/goal_src/engine/anim/bones.gc +++ b/goal_src/engine/anim/bones.gc @@ -15,7 +15,7 @@ ;; It's more than just bones in here - submitting to merc is done from here. -(defglobalconstant USE_GENERIC #t) +(defglobalconstant BACKWARD_COMPAT_MERC_CLIP #f) ;;;;;;;;;;;;;;;;;; ;; calc list @@ -330,7 +330,7 @@ (let ((a2-1 (the-as bone-memory (+ 16 (scratchpad-object int)))) (v1-2 (the-as bone-memory (+ 16 (scratchpad-object int)))) ) - + ;; layout joints, bones, and outputs (set! (-> a2-1 work layout joint 0) (the-as joint (+ 256 (scratchpad-object int)))) (set! (-> a2-1 work layout joint 1) (the-as joint (+ 4864 (scratchpad-object int)))) @@ -338,7 +338,7 @@ (set! (-> a2-1 work layout bone 1) (the-as bone (+ 5888 (scratchpad-object int)))) (set! (-> a2-1 work layout output 0) (the-as uint (+ 2816 (scratchpad-object int)))) (set! (-> a2-1 work layout output 1) (the-as uint (+ 7424 (scratchpad-object int)))) - + ;; set up work (set! (-> v1-2 work next-tag dma) (new 'static 'dma-tag :id (dma-tag-id next))) (set! (-> v1-2 work next-tag vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) @@ -347,13 +347,13 @@ (set! (-> v1-2 work sink-group) arg1) (set! (-> v1-2 work next-merc) (the-as dma-packet 0)) ) - + ;; reset globals (let ((v1-3 *merc-globals*)) (set! (-> v1-3 first) (the-as uint 0)) (set! (-> v1-3 next) (the-as (pointer uint32) 0)) ) - + ;; upload bones program. (#unless PC_PORT (let ((gp-0 *vu0-dma-list*)) @@ -373,7 +373,7 @@ (dma-buffer-send-chain (the-as dma-bank-source #x10008000) gp-0) ) ) - + ;; we will use "run" in the shadow queue. Reset that (but don't increment yet, just in case we don't draw shadows) (let ((gp-1 *shadow-queue*)) (if (>= (-> gp-1 cur-run) (the-as uint 15)) @@ -476,11 +476,11 @@ (.lvf vf25 (&-> v1-13 vector 0 quad)) (.lvf vf26 (&-> v1-13 vector 1 quad)) (.lvf vf27 (&-> v1-13 vector 2 quad)) - + (.mov v1-14 vf27) ;; hack?? - - + + (bones-mtx-calc (the-as int (-> s4-0 matrix-area)) (the-as pointer (-> s4-0 joints)) @@ -889,7 +889,7 @@ ; (let ((vec (the vector (+ (the-as int a3-2) 16)))) ; (format 0 "#x~X #x~X mat ~D: ~f ~f ~f~%" a0-55 a3-2 a1-17 (-> vec x) (-> vec y) (-> vec z)) ; ) - + ) (set! (-> (the-as dma-packet a0-55) vif0) (new 'static 'vif-tag)) (set! (-> (the-as dma-packet a0-55) vif1) (new 'static 'vif-tag)) @@ -936,6 +936,35 @@ `(set! (-> (the-as (pointer uint32) ,addr)) ,val) ) +(defun pc-draw-bones ((dc draw-control) (dma-buf pointer)) + "Add a dma packet to tell the PC renderer which model we are renderering." + (let ((packet (the-as dma-packet dma-buf))) + ;; merc draw asm will check this. + (when (zero? (-> (scratchpad-object terrain-context) work foreground bone-mem work next-merc)) + (set! (-> (scratchpad-object terrain-context) work foreground bone-mem work next-merc) packet) + ) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 10)) + (set! (-> packet vif0) (new 'static 'vif-tag)) + (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) + (set! dma-buf (the pointer (&+ packet 16))) + ) + + (let ((data-ptr (the-as (pointer uint128) dma-buf))) + (charp<-string (the (pointer uint8) (&-> data-ptr 0)) (-> dc mgeo name)) + ) + (&+! dma-buf (* 16 10)) + + ;; merc linking needs this. + (let ((packet (the-as dma-packet dma-buf))) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 0)) + (set! (-> packet vif0) (new 'static 'vif-tag)) + (set! (-> packet vif1) (new 'static 'vif-tag)) + (set! dma-buf (the pointer (&+ packet 16))) + ) + + dma-buf + ) + (defun draw-bones ((arg0 draw-control) (dma-buf dma-buffer) (arg2 float)) "Main draw function for all bone-related renderers. Will set up merc, generic and shadow. and also add the bones to the calculation list." @@ -943,7 +972,7 @@ (used-merc int) (used-mercneric int) (effect-idx int) (sv-144 ripple-control)) (rlet ((vf1 :class vf) (vf2 :class vf) (vf3 :class vf) (vf4 :class vf) (vf5 :class vf) (vf6 :class vf) (vf7 :class vf) (vf8 :class vf) (vf9 :class vf) (acc :class vf)) - + ;; compute the number of bones. I believe this num-joints doesn't count align/prejoint/main, so each needs a bone (let* ((num-bones (+ (-> arg0 mgeo num-joints) 3)) ;; we'll use 128-bytes/bone. @@ -951,11 +980,11 @@ ;; temp work (spr-work (scratchpad-object terrain-context)) ) - + ;;;;;;;;;;;;;;;;;;;;;;;;; ;; BONE CALC SETUP ;;;;;;;;;;;;;;;;;;;;;;;;; - + ;; next, grab some DMA data. ;; we'll also use this for storing bone calculation entries. (let* ((dma-data-start (-> dma-buf base)) @@ -965,14 +994,14 @@ ;; we won't calculate them now, but instead we'll tell the *bone-calculation-list* to put them here later. (matrix-data (the-as object (&+ dma-data-start 64))) ) - + ;; align the matrix data to 64 bytes (we know it is at least 16 byte aligned) (let ((a2-1 (logand (the-as int matrix-data) 48))) (b! (zero? a2-1) cfg-2 :delay (nop!)) (set! matrix-data (&- (&+ (the-as pointer matrix-data) 64) (the-as uint a2-1))) ) (label cfg-2) - + ;; start recording perf stats. ;; this just counts the stats for adding the calculation to the linked list. ;; which is really fast and not worth profiling. @@ -989,7 +1018,7 @@ (set! (-> a2-6 bone-ptr) (-> arg0 skeleton bones)) (set! (-> a2-6 num-bones) (the-as uint num-bones)) ) - + ;; Add the bone calculation to the list, to be done later. ;; this will write to both the "matrix data" and the bones array. (let ((t0-2 matrix-data) @@ -1021,7 +1050,7 @@ (&+ bone-calc-entry 48) ;; end stat collection (read! (-> *perf-stats* data (perf-stat-bucket bones))) - + ;; set up the dma buffer (let ((s2-0 (the-as object (+ (the-as uint matrix-data) bone-data-size)))) (let ((a0-2 (shl (the-as int s2-0) 32))) @@ -1030,15 +1059,25 @@ ) ;; NOTE: does this work correctly for the upper 64 bits?? ) - + ;; only data-format 1 is supported. (when (= (-> arg0 data-format) 1) + + ;; we'll be filling out the *merc-bucket-info*, then calling the draw asm functions + ;; the asm functions read *merc-bucket-info* and generate DMA data as needed. + + ;;;;;;;;;;;;;;;;;;;;;;;;; + ;; LIGHT SETUP + ;;;;;;;;;;;;;;;;;;;;;;;;; + ;; lights should be converted to a single vu-lights already, by the drawing code. ;; it stashes lights on the scratchpad. We'll load these here and transform them. - ;; the result goes in the merc-bucke-info. + ;; the result goes in the merc-bucket-info. (let ((v1-6 (the-as vu-lights (+ 64 (scratchpad-object int)))) (a0-7 (-> *merc-bucket-info* light)) ) + ;; merc does drawing in a camera rotated frame, so multiply by inv-rot to have the right directions + ;; relative to the camera. (let ((a1-8 (-> *math-camera* inv-camera-rot))) (.lvf vf4 (&-> a1-8 vector 0 quad)) (.lvf vf5 (&-> a1-8 vector 1 quad)) @@ -1047,30 +1086,29 @@ (.lvf vf1 (&-> v1-6 direction 0 quad)) (.lvf vf2 (&-> v1-6 direction 1 quad)) (.lvf vf3 (&-> v1-6 direction 2 quad)) - ;;(.vcallms 54) - ;; TODO! - - ;mulax.xyzw ACC, vf01, vf04 - (.mul.x.vf acc vf1 vf4) - ;madday.xyzw ACC, vf02, vf04 - (.add.mul.y.vf acc vf2 vf4 acc) - ;maddz.xyzw vf07, vf03, vf04 - (.add.mul.z.vf vf7 vf3 vf4 acc) - - ;mulax.xyzw ACC, vf01, vf05 - (.mul.x.vf acc vf1 vf5) - ;madday.xyzw ACC, vf02, vf05 - (.add.mul.y.vf acc vf2 vf5 acc) - ;maddz.xyzw vf08, vf03, vf05 - (.add.mul.z.vf vf8 vf3 vf5 acc) - - ;mulax.xyzw ACC, vf01, vf06 - (.mul.x.vf acc vf1 vf6) - ;madday.xyzw ACC, vf02, vf06 :e - (.add.mul.y.vf acc vf2 vf6 acc) - ;maddz.xyzw vf09, vf03, vf06 - (.add.mul.z.vf vf9 vf3 vf6 acc) - + ;;(.vcallms 54) ;; manually ported to goal here. + + ;mulax.xyzw ACC, vf01, vf04 + (.mul.x.vf acc vf1 vf4) + ;madday.xyzw ACC, vf02, vf04 + (.add.mul.y.vf acc vf2 vf4 acc) + ;maddz.xyzw vf07, vf03, vf04 + (.add.mul.z.vf vf7 vf3 vf4 acc) + + ;mulax.xyzw ACC, vf01, vf05 + (.mul.x.vf acc vf1 vf5) + ;madday.xyzw ACC, vf02, vf05 + (.add.mul.y.vf acc vf2 vf5 acc) + ;maddz.xyzw vf08, vf03, vf05 + (.add.mul.z.vf vf8 vf3 vf5 acc) + + ;mulax.xyzw ACC, vf01, vf06 + (.mul.x.vf acc vf1 vf6) + ;madday.xyzw ACC, vf02, vf06 :e + (.add.mul.y.vf acc vf2 vf6 acc) + ;maddz.xyzw vf09, vf03, vf06 + (.add.mul.z.vf vf9 vf3 vf6 acc) + (let ((a1-9 (-> v1-6 color 0 quad))) (let ((a2-14 (-> v1-6 color 1 quad))) (let ((a3-11 (-> v1-6 color 2 quad))) @@ -1088,62 +1126,85 @@ (.svf (&-> a0-7 direction 2 quad) vf9) (set! (-> a0-7 direction 1 w) (the-as float 0)) ) - + ;; the drawable system's culling already knows if we need clipping or not, - ;; we just trust it. - (set! (-> *merc-bucket-info* needs-clip) + ;; we just trust it. Anything needing clip will have this set. It might set it for + ;; meshes that actually don't need clipping. + (set! (-> *merc-bucket-info* needs-clip) (if (logtest? (-> arg0 status) (draw-status needs-clip)) 1 0 ) ) - + ;; Now, we need to decide on merc settings. Default to nothing. (set! used-merc 0) (set! used-mercneric 0) + ;; clipping is really slow because merc vertex interpolation is slow + ;; so we have an option to just let the GS scissor, called mercprime. + ;; it requires a special codepath in merc called mercprime. (set! (-> *merc-bucket-info* need-mercprime-if-merc) 0) + ;; if we're really out of bounds, mercprime will fail, and we need to fall back to mercneric + ;; in order to do clipping. (set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 0) - - (when (logtest? (-> arg0 status) (draw-status needs-clip)) - ;; we can either clip with "mercprime" (part of merc), or fall back to generic. - ;; the generic approach always works, but is slower, so we try to use mercprime. - ;; the user must provide a longest edge length, and we must pass an edge check (fails if close to camera) - (cond - ((nonzero? (-> arg0 longest-edge)) - ;; TODO this makes close up things bad... - (if (draw-bones-check-longest-edge-asm arg0 arg2) - (set! (-> *merc-bucket-info* need-mercprime-if-merc) 1) ;; clip with mercprime! - (set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; failed, use generic + + ;;;;;;;;;;;;;;;;;;; + ;; CLIPPING TEST + ;; in the PC port merc2 renderer, we handle clipping perfectly without any special flags, and it's the fastest. + ;; so we only need to worry about mercneric/mercprime if we want to maintain backward compatibility with original merc + (#when BACKWARD_COMPAT_MERC_CLIP + (when (logtest? (-> arg0 status) (draw-status needs-clip)) + ;; we can either clip with "mercprime" (part of merc), or fall back to generic. + ;; the generic approach always works, but is slower, so we try to use mercprime. + ;; the user must provide a longest edge length, and we must pass an edge check (fails if close to camera) + (cond + ((nonzero? (-> arg0 longest-edge)) + ;; check to see if the edge will be too long: + (if (draw-bones-check-longest-edge-asm arg0 arg2) + (set! (-> *merc-bucket-info* need-mercprime-if-merc) 1) ;; use mercprime! + (set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; failed, use generic and actually scissor + ) + ) + (else + ;; in cases where no longest edge is set, fall back to mercneric for clipping always + (set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) + ) + ) + ) ) - ) - (else - (set! (-> *merc-bucket-info* must-use-mercneric-for-clip) 1) ;; no longest edge info, use generic. - ) - ) - ) - + ;; grab the geometry at the appropriate level of detail. - (let ((geom (-> arg0 lod-set lod (-> arg0 cur-lod) geo))) + (let ((geom (-> arg0 lod-set lod (-> arg0 cur-lod) geo)) + ;; merc2 can't handle all cases of the original merc, so we add this fallback on PC. + (pc-force-mercneric #f) + ) ;; loop over effects, and set them up/pick renderers. (set! effect-idx 0) (while (< effect-idx (the-as int (-> geom header effect-count))) - - ;; try texture scroll + + ;; check if this "effect" uses texture scrolling (when (logtest? (-> geom effect effect-idx effect-bits) 1) + ;; grab the extra info for it: (let* ((v1-35 (-> geom effect effect-idx extra-info)) (v1-36 (the-as mei-texture-scroll (+ (the-as uint v1-35) (* (-> v1-35 texture-scroll-offset) 16)))) ) - (if (< arg2 (-> v1-36 max-dist)) - ;; just add to texscroll list. + (when (< arg2 (-> v1-36 max-dist)) ;; in range to draw? + ;; do scrolling. in PC, always use generic here because merc2 can't update texture coordinates + (#when PC_PORT + (set! pc-force-mercneric #t) + ) (texscroll-make-request (-> geom effect effect-idx)) ) ) ) - - ;; try ripple + + ;; check if this effect uses "ripple" (when (logtest? (-> geom effect effect-idx effect-bits) 4) - ;; TODO. (when (-> arg0 ripple) + ;; merc2 can't handle ripple, even if it's off. + (#when PC_PORT + (set! pc-force-mercneric #t) + ) (set! sv-144 (-> arg0 ripple)) (let* ((f1-4 (/ (- (-> sv-144 far-fade-dist) arg2) (- (-> sv-144 far-fade-dist) (-> sv-144 close-fade-dist)))) (f1-6 (fmax 0.0 (fmin 1.0 f1-4))) @@ -1182,17 +1243,45 @@ ) ) ) - - + + ;; additional check on PC to force mercneric for blend shapes + (#when PC_PORT + (let* ((pd (the process-drawable (-> arg0 process))) + (jc (-> pd skel))) + (when (nonzero? jc) + (when (logtest? (-> jc status) (janim-status blerc)) + (set! pc-force-mercneric #t) + ) + ) + ) + ) + + + ;; final mercneric checks (cond - ((nonzero? (-> arg0 death-timer)) - ;; if using hte death effect, use mercneric always! + ((nonzero? (-> arg0 death-timer)) ;; death effect? + ;; use mercneric always! (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1)) (set! used-mercneric 1) used-mercneric ) ((nonzero? (-> geom effect effect-idx envmap-usage)) ;; if we need envmap, set it up. + + ;; hack: + ;; the game switches from mercneric to merc after the envmap is faded out. + ;; this would normally be fine in the PC port too, but we want the eyes to remain + ;; using mercneric (dynamic texture updates) in PC. + ;; so we force mercneric on models with envmap and very few tris. + ;; it seems to work so far... + (#when PC_PORT + (when (and + (< (-> geom effect effect-idx tri-count) 100) + (-> geom header eye-ctrl) + ) + (set! pc-force-mercneric #t) + ) + ) (let* ((v1-83 (-> geom effect effect-idx extra-info)) ;; pointer to envmap tint data (v1-84 (the-as structure (+ (the-as uint v1-83) (* (-> v1-83 envmap-tint-offset) 16)))) @@ -1208,10 +1297,11 @@ (if (< f0-9 0.0) (set! f0-9 0.0) ) - + (cond ;; do envmap stuff if we are using generic anyway, or our strength is > 0. - ((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) (< 0.0 f0-9)) + ((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) (< 0.0 f0-9) pc-force-mercneric) + ;; multiply colors by sun tint. (let ((v1-85 (&-> (the-as merc-extra-info v1-84) dummy 4)) (a0-36 (the-as object (-> *merc-bucket-info* effect effect-idx))) @@ -1249,8 +1339,7 @@ ) ) ;; final two cases are for all other effects that don't matter generic/normal - ;; here is a good place to force mercneric. - ((nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) + ((or (nonzero? (-> *merc-bucket-info* must-use-mercneric-for-clip)) pc-force-mercneric) (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1)) (set! used-mercneric 1) used-mercneric @@ -1262,54 +1351,34 @@ used-merc ) ) - - ;; HACK just use merc if we aren't using generic. - (#unless USE_GENERIC - (when (logtest? *vu1-enable-user* (vu1-renderer-mask generic)) - (unless (logtest? (-> arg0 status) (draw-status needs-clip)) - ;; no clip, but wants generic. Just use merc for now. - (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 0)) - (set! used-merc 1) - ) - (when (and (logtest? (-> arg0 status) (draw-status needs-clip)) ;; we need to clip - ;;(nonzero? (-> arg0 longest-edge)) ;; we known the longest edge - ;;(draw-bones-check-longest-edge-asm arg0 arg2) ;; it's ok to use percprime. - ) - ;; wants clip. in all cases, give to merc. this might give it to merc even if it fails the - ;; long edge check, but it does ok - (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 0)) - (set! (-> *merc-bucket-info* need-mercprime-if-merc) 1) - (set! used-merc 1) - ) - ) - ) - + + ;; final force to mercneric for ps + (when pc-force-mercneric + (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1)) + (set! used-mercneric 1) + ) + (set! effect-idx (+ effect-idx 1)) ) ;; end effect loop - + ;; draw generic! - ;; note: this doesn't do the fully draw, there's some other stuff in process-drawable.gc to actually execute. - (#when USE_GENERIC - (when (nonzero? used-mercneric) - (when (logtest? *vu1-enable-user* (vu1-renderer-mask generic)) - (set! (-> dma-buf base) (draw-bones-generic-merc arg0 (the pointer matrix-data) (the pointer s2-0) 0)) - (set! s2-0 (-> dma-buf base)) - ) + ;; note: this doesn't do the fully draw, there's some other stuff in process-drawable.gc to actually execute. + (when (nonzero? used-mercneric) + (when (logtest? *vu1-enable-user* (vu1-renderer-mask generic)) + (set! (-> dma-buf base) (draw-bones-generic-merc arg0 (the pointer matrix-data) (the pointer s2-0) 0)) + (set! s2-0 (-> dma-buf base)) ) ) - + ;; draw shadow! - ;; TODO (when (-> arg0 shadow) (when #t (set! s2-0 (draw-bones-shadow arg0 (the pointer matrix-data) (the pointer s2-0))) (set! (-> dma-buf base) (the-as pointer s2-0)) ) ) - + ;; draw merc! - - ;; (when (nonzero? used-merc) (when (logtest? *vu1-enable-user* (vu1-renderer-mask merc)) (when (= (-> arg0 cur-lod) (-> arg0 lod-set max-lod)) @@ -1328,6 +1397,7 @@ ) ) ) + (set! s2-0 (pc-draw-bones arg0 (the pointer s2-0))) (if (nonzero? (-> *merc-bucket-info* need-mercprime-if-merc)) (set! (-> dma-buf base) (draw-bones-merc arg0 matrix-data s2-0 32 17)) (set! (-> dma-buf base) (draw-bones-merc arg0 matrix-data s2-0 35 20)) @@ -1336,7 +1406,7 @@ ) ) ) ;; end geom processing - + ;; advance death timer. (when (nonzero? (-> arg0 death-timer)) (when (not (paused?))