From 72c27a6eaa9ef019288660b4e4565a343e102d68 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 16 Jul 2023 13:02:53 -0400 Subject: [PATCH] [jak2] More texture animations (#2831) Added framework to do texture animations entirely in C++. Currently only works on relatively simple ones, and doesn't handle updating all parameters - only the speeds. Connected texture animations to merc and tfrag for skull gems, dark bomb, and scrolling conveyors. Cleaned up Tfragment/Tfrag3, which used to be two classes. This was one of the first C++ renderers, so it had a weird design. --- common/custom_data/TFrag3Data.cpp | 3 + common/custom_data/Tfrag3Data.h | 2 +- common/texture/texture_slots.cpp | 6 + decompiler/config/jak2/ntsc_v1/inputs.jsonc | 20 +- decompiler/data/tpage.cpp | 6 + decompiler/extractor/main.cpp | 4 +- decompiler/level_extractor/extract_level.cpp | 57 +- decompiler/level_extractor/extract_level.h | 2 +- decompiler/level_extractor/extract_merc.cpp | 4 - decompiler/level_extractor/extract_tfrag.cpp | 104 ++- decompiler/main.cpp | 4 +- game/CMakeLists.txt | 1 - .../opengl_renderer/OpenGLRenderer.cpp | 31 +- .../graphics/opengl_renderer/OpenGLRenderer.h | 4 + game/graphics/opengl_renderer/SkyRenderer.cpp | 6 +- game/graphics/opengl_renderer/SkyRenderer.h | 3 +- .../opengl_renderer/TextureAnimator.cpp | 776 ++++++++++++++++-- .../opengl_renderer/TextureAnimator.h | 135 +-- .../opengl_renderer/background/TFragment.cpp | 524 +++++++++++- .../opengl_renderer/background/TFragment.h | 110 ++- .../opengl_renderer/background/Tfrag3.cpp | 520 ------------ .../opengl_renderer/background/Tfrag3.h | 121 --- .../opengl_renderer/shaders/tex_anim.frag | 3 + .../jak2/engine/gfx/texture/texture-anim.gc | 76 ++ 24 files changed, 1676 insertions(+), 846 deletions(-) delete mode 100644 game/graphics/opengl_renderer/background/Tfrag3.cpp delete mode 100644 game/graphics/opengl_renderer/background/Tfrag3.h diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index 285aa49b25..d0cd2d443e 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -720,6 +720,9 @@ void Level::memory_usage(MemoryUsageTracker* tracker) const { for (const auto& texture : textures) { texture.memory_usage(tracker); } + for (const auto& texture : index_textures) { + texture.memory_usage(tracker); + } for (const auto& tftk : tfrag_trees) { for (const auto& tree : tftk) { tree.memory_usage(tracker); diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 680d36aaa7..ec09c9cdd2 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -180,7 +180,7 @@ struct PackedShrubVertices { // check visibility. struct StripDraw { DrawMode mode; // the OpenGL draw settings. - u32 tree_tex_id = 0; // the texture that should be bound for the draw + s32 tree_tex_id = 0; // the texture that should be bound for the draw (negative for anim slot) struct { u32 idx_of_first_idx_in_full_buffer = 0; diff --git a/common/texture/texture_slots.cpp b/common/texture/texture_slots.cpp index 4849fb9ece..83e37c0471 100644 --- a/common/texture/texture_slots.cpp +++ b/common/texture/texture_slots.cpp @@ -22,6 +22,12 @@ std::vector jak2_slots = { //"kor-head-formorph-noreflect", //"kor-lowercaps-formorph", //"kor-uppercaps-formorph", + "skull-gem-dest", + "bomb-gradient", + "cas-conveyor-dest", + "cas-conveyor-dest-01", + "cas-conveyor-dest-02", + "cas-conveyor-dest-03", }; } diff --git a/decompiler/config/jak2/ntsc_v1/inputs.jsonc b/decompiler/config/jak2/ntsc_v1/inputs.jsonc index 09b22bc45d..2892963fb9 100644 --- a/decompiler/config/jak2/ntsc_v1/inputs.jsonc +++ b/decompiler/config/jak2/ntsc_v1/inputs.jsonc @@ -491,7 +491,25 @@ "jakb-eyelid-dark", "jakb-finger", "jakb-finger-norm", - "jakb-finger-dark" + "jakb-finger-dark", + + // Skull Gem + "skull-gem-dest", + "skull-gem-alpha-00", + "skull-gem-alpha-01", + "skull-gem-alpha-02", + + // BOMB + "bomb-gradient", + "bomb-gradient-rim", + "bomb-gradient-flames", + + // CAS conveyor + "cas-conveyor", + "cas-conveyor-dest", + "cas-conveyor-dest-01", + "cas-conveyor-dest-02", + "cas-conveyor-dest-03" // "kor-eyeeffect-formorph", // "kor-eyeeffect-formorph-start", diff --git a/decompiler/data/tpage.cpp b/decompiler/data/tpage.cpp index b9734db993..005e14a578 100644 --- a/decompiler/data/tpage.cpp +++ b/decompiler/data/tpage.cpp @@ -504,6 +504,12 @@ TPageResultStats process_tpage(ObjectFileData& data, if (animated_textures.count(tex.name) && !ignore_animated) { switch (tex.psm) { + case int(PSM::PSMCT32): + // no need. + break; + case int(PSM::PSMT4): + // currently not needed. + break; case int(PSM::PSMT8): ASSERT(tex.clutpsm == int(CPSM::PSMCT32)); { diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index a03c9eb6cc..278828418f 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -183,8 +183,8 @@ void decompile(const fs::path& iso_data_path, const std::string& data_subfolder) auto level_out_path = file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3"; file_util::create_dir_if_needed(level_out_path); - extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks, - config.rip_levels, config.extract_collision, level_out_path); + extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, config.rip_levels, + config.extract_collision, level_out_path); } } diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 0ce001da35..94c8a4af4e 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -71,6 +71,22 @@ bool is_valid_bsp(const decompiler::LinkedObjectFile& file) { return true; } +tfrag3::Texture make_texture(u32 id, + const TextureDB::TextureData& tex, + const std::string& tpage_name, + bool pool_load) { + tfrag3::Texture new_tex; + new_tex.combo_id = id; + new_tex.w = tex.w; + new_tex.h = tex.h; + new_tex.debug_tpage_name = tpage_name; + new_tex.debug_name = tex.name; + new_tex.data = tex.rgba_bytes; + new_tex.combo_id = id; + new_tex.load_to_pool = pool_load; + return new_tex; +} + void add_all_textures_from_level(tfrag3::Level& lev, const std::string& level_name, const TextureDB& tex_db) { @@ -79,16 +95,7 @@ void add_all_textures_from_level(tfrag3::Level& lev, if (level_it != tex_db.texture_ids_per_level.end()) { for (auto id : level_it->second) { const auto& tex = tex_db.textures.at(id); - lev.textures.emplace_back(); - auto& new_tex = lev.textures.back(); - new_tex.combo_id = id; - new_tex.w = tex.w; - new_tex.h = tex.h; - new_tex.debug_tpage_name = tex_db.tpage_names.at(tex.page); - new_tex.debug_name = tex.name; - new_tex.data = tex.rgba_bytes; - new_tex.combo_id = id; - new_tex.load_to_pool = true; + lev.textures.push_back(make_texture(id, tex, tex_db.tpage_names.at(tex.page), true)); } } } @@ -224,7 +231,8 @@ void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, bool dump_levels, - const fs::path& output_folder) { + const fs::path& output_folder, + const Config& config) { if (db.obj_files_by_dgo.count(dgo_name) == 0) { lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name); return; @@ -241,11 +249,28 @@ void extract_common(const ObjectFileDB& db, add_all_textures_from_level(tfrag_level, dgo_name, tex_db); extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level); + std::set textures_we_have; + // put _all_ index textures in common. for (const auto& [id, tex] : tex_db.index_textures_by_combo_id) { tfrag_level.index_textures.push_back(tex); } + for (const auto& t : tfrag_level.textures) { + textures_we_have.insert(t.debug_name); + } + + // add animated textures that are missing. + for (const auto& [id, normal_texture] : tex_db.textures) { + if (config.animated_textures.count(normal_texture.name) && + !textures_we_have.count(normal_texture.name)) { + lg::warn("adding anim normal texture {} ", normal_texture.name); + textures_we_have.insert(normal_texture.name); + tfrag_level.textures.push_back( + make_texture(id, normal_texture, tex_db.tpage_names.at(normal_texture.page), false)); + } + } + Serializer ser; tfrag_level.serialize(ser); auto compressed = @@ -269,7 +294,7 @@ void extract_common(const ObjectFileDB& db, void extract_from_level(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, - const DecompileHacks& hacks, + const Config& config, bool dump_level, bool extract_collision, const fs::path& output_folder) { @@ -282,7 +307,7 @@ void extract_from_level(const ObjectFileDB& db, // the bsp header file data auto tex_remap = - extract_bsp_from_level(db, tex_db, dgo_name, hacks, extract_collision, level_data); + extract_bsp_from_level(db, tex_db, dgo_name, config.hacks, extract_collision, level_data); extract_art_groups_from_level(db, tex_db, tex_remap, dgo_name, level_data); Serializer ser; @@ -313,15 +338,15 @@ void extract_all_levels(const ObjectFileDB& db, const TextureDB& tex_db, const std::vector& dgo_names, const std::string& common_name, - const DecompileHacks& hacks, + const Config& config, bool debug_dump_level, bool extract_collision, const fs::path& output_path) { - extract_common(db, tex_db, common_name, debug_dump_level, output_path); + extract_common(db, tex_db, common_name, debug_dump_level, output_path, config); SimpleThreadGroup threads; threads.run( [&](int idx) { - extract_from_level(db, tex_db, dgo_names[idx], hacks, debug_dump_level, extract_collision, + extract_from_level(db, tex_db, dgo_names[idx], config, debug_dump_level, extract_collision, output_path); }, dgo_names.size()); diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index 169aa760dc..629d4fdfa8 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -13,7 +13,7 @@ void extract_all_levels(const ObjectFileDB& db, const TextureDB& tex_db, const std::vector& dgo_names, const std::string& common_name, - const DecompileHacks& hacks, + const Config& config, bool debug_dump_level, bool extract_collision, const fs::path& path); diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index 716d4acf26..0f303cf50d 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -796,12 +796,8 @@ s32 find_or_add_texture_to_level(tfrag3::Level& out, const auto& level_tex = out.textures.at(idx_in_level_texture); const auto& it = tex_db.animated_tex_output_to_anim_slot.find(level_tex.debug_name); if (it != tex_db.animated_tex_output_to_anim_slot.end()) { - lg::error("Animated slot {} -> {}", level_tex.debug_name, it->second); return -int(it->second) - 1; - } else { - // lg::warn("no anim: {}", level_tex.debug_name); } - return idx_in_level_texture; } diff --git a/decompiler/level_extractor/extract_tfrag.cpp b/decompiler/level_extractor/extract_tfrag.cpp index 83cd9c9ac1..55e178a577 100644 --- a/decompiler/level_extractor/extract_tfrag.cpp +++ b/decompiler/level_extractor/extract_tfrag.cpp @@ -2007,6 +2007,65 @@ std::map> make_draw_groups(std::vector& return result; } +s32 find_or_add_texture_to_level(u32 combo_tex_id, + std::vector& texture_pool, + const TextureDB& tdb, + const std::vector>& expected_missing_textures, + const std::string& level_name) { + // first, let's see if we have a texture for this. + s32 tfrag3_tex_id = INT32_MAX; + for (u32 i = 0; i < texture_pool.size(); i++) { + if (texture_pool[i].combo_id == combo_tex_id) { + tfrag3_tex_id = i; + break; + } + } + + if (tfrag3_tex_id == INT32_MAX) { + // nope. we are a new texture. + auto tex_it = tdb.textures.find(combo_tex_id); + if (tex_it == tdb.textures.end()) { + int tpage = combo_tex_id >> 16; + int idx = combo_tex_id & 0xffff; + bool ok_to_miss = + std::find(expected_missing_textures.begin(), expected_missing_textures.end(), + std::make_pair(tpage, idx)) != expected_missing_textures.end(); + if (ok_to_miss) { + // we're missing a texture, just use the first one. + tex_it = tdb.textures.begin(); + } else { + ASSERT_MSG( + false, + fmt::format("texture {} wasn't found. make sure it is loaded somehow. You may need " + "to include " + "ART.DGO or GAME.DGO in addition to the level DGOs for shared textures." + "tpage is {}. id is {} (0x{:x}) for level {}", + combo_tex_id, combo_tex_id >> 16, combo_tex_id & 0xffff, + combo_tex_id & 0xffff, level_name)); + } + } + tfrag3_tex_id = texture_pool.size(); + texture_pool.emplace_back(); + auto& new_tex = texture_pool.back(); + new_tex.combo_id = 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 = tdb.tpage_names.at(tex_it->second.page); + new_tex.data = tex_it->second.rgba_bytes; + } + + // map animated textures to the animation slot. + const auto& level_tex = texture_pool.at(tfrag3_tex_id); + const auto& it = tdb.animated_tex_output_to_anim_slot.find(level_tex.debug_name); + if (it != tdb.animated_tex_output_to_anim_slot.end()) { + // lg::warn("tfrag3 animated texture: {}", level_tex.debug_name); + return -int(it->second) - 1; + } + + return tfrag3_tex_id; +} + void make_tfrag3_data(std::map>& draws, tfrag3::TfragTree& tree_out, std::vector& vertices, @@ -2020,49 +2079,8 @@ void make_tfrag3_data(std::map>& draws, // and link textures. for (auto& [combo_tex_id, draw_list] : draws) { - // first, let's see if we have a texture for this. - u32 tfrag3_tex_id = UINT32_MAX; - for (u32 i = 0; i < texture_pool.size(); i++) { - if (texture_pool[i].combo_id == combo_tex_id) { - tfrag3_tex_id = i; - break; - } - } - - if (tfrag3_tex_id == UINT32_MAX) { - // nope. we are a new texture. - auto tex_it = tdb.textures.find(combo_tex_id); - if (tex_it == tdb.textures.end()) { - int tpage = combo_tex_id >> 16; - int idx = combo_tex_id & 0xffff; - bool ok_to_miss = - std::find(expected_missing_textures.begin(), expected_missing_textures.end(), - std::make_pair(tpage, idx)) != expected_missing_textures.end(); - if (ok_to_miss) { - // we're missing a texture, just use the first one. - tex_it = tdb.textures.begin(); - } else { - ASSERT_MSG( - false, - fmt::format("texture {} wasn't found. make sure it is loaded somehow. You may need " - "to include " - "ART.DGO or GAME.DGO in addition to the level DGOs for shared textures." - "tpage is {}. id is {} (0x{:x}) for level {}", - combo_tex_id, combo_tex_id >> 16, combo_tex_id & 0xffff, - combo_tex_id & 0xffff, level_name)); - } - } - tfrag3_tex_id = texture_pool.size(); - texture_pool.emplace_back(); - auto& new_tex = texture_pool.back(); - new_tex.combo_id = 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 = tdb.tpage_names.at(tex_it->second.page); - new_tex.data = tex_it->second.rgba_bytes; - } - + s32 tfrag3_tex_id = find_or_add_texture_to_level(combo_tex_id, texture_pool, tdb, + expected_missing_textures, level_name); // now, add draws for (auto& draw : draw_list) { tfrag3::StripDraw tdraw; diff --git a/decompiler/main.cpp b/decompiler/main.cpp index e1342e0487..2f60492bd2 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -294,8 +294,8 @@ int main(int argc, char** argv) { auto level_out_path = file_util::get_jak_project_dir() / "out" / game_version_names[config.game_version] / "fr3"; file_util::create_dir_if_needed(level_out_path); - extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config.hacks, - config.rip_levels, config.extract_collision, level_out_path); + extract_all_levels(db, tex_db, config.levels_to_extract, "GAME.CGO", config, config.rip_levels, + config.extract_collision, level_out_path); } mem_log("After extraction: {} MB", get_peak_rss() / (1024 * 1024)); diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 3126ed8cc0..e39b8ce646 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -35,7 +35,6 @@ set(RUNTIME_SOURCE graphics/jak2_texture_remap.cpp graphics/opengl_renderer/background/background_common.cpp graphics/opengl_renderer/background/Shrub.cpp - graphics/opengl_renderer/background/Tfrag3.cpp graphics/opengl_renderer/background/TFragment.cpp graphics/opengl_renderer/background/Tie3.cpp graphics/opengl_renderer/BlitDisplays.cpp diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index db82a53e4b..d45b3a379b 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -102,8 +102,7 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool, ASSERT(false); } - m_merc2 = std::make_shared(m_render_state.shaders, - m_texture_animator ? m_texture_animator->slots() : nullptr); + m_merc2 = std::make_shared(m_render_state.shaders, anim_slot_array()); m_generic2 = std::make_shared(m_render_state.shaders); // initialize all renderers @@ -143,7 +142,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tfrag-l{}-tfrag", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_L0_TFRAG, BucketId::TFRAG_L1_TFRAG, i), - std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i); + std::vector{tfrag3::TFragmentTreeKind::NORMAL}, false, i, anim_slot_array()); Tie3* tie = init_bucket_renderer( fmt::format("tie-l{}-tfrag", i), BucketCategory::TIE, GET_BUCKET_ID_FOR_LIST(BucketId::TIE_L0_TFRAG, BucketId::TIE_L1_TFRAG, i), i); @@ -181,7 +180,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tfrag-t-l{}-alpha", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_T_L0_ALPHA, BucketId::TFRAG_T_L1_ALPHA, i), - std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i); + std::vector{tfrag3::TFragmentTreeKind::TRANS}, false, i, anim_slot_array()); init_bucket_renderer( fmt::format("tie-t-l{}-alpha", i), BucketCategory::TIE, GET_BUCKET_ID_FOR_LIST(BucketId::TIE_T_L0_ALPHA, BucketId::TIE_T_L1_ALPHA, i), tie, @@ -236,7 +235,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tfrag-w-l{}-alpha", i), BucketCategory::TFRAG, GET_BUCKET_ID_FOR_LIST(BucketId::TFRAG_W_L0_WATER, BucketId::TFRAG_W_L1_WATER, i), - std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i); + std::vector{tfrag3::TFragmentTreeKind::WATER}, false, i, anim_slot_array()); init_bucket_renderer( fmt::format("tie-w-l{}-water", i), BucketCategory::TIE, GET_BUCKET_ID_FOR_LIST(BucketId::TIE_W_L0_WATER, BucketId::TIE_W_L1_WATER, i), tie, @@ -355,7 +354,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { BucketId::TFRAG_TEX_LEVEL0, m_texture_animator); // 6 : TFRAG_LEVEL0 init_bucket_renderer("l0-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL0, - normal_tfrags, false, 0); + normal_tfrags, false, 0, anim_slot_array()); // 7 : TFRAG_NEAR_LEVEL0 // 8 : TIE_NEAR_LEVEL0 // 9 : TIE_LEVEL0 @@ -377,7 +376,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { BucketId::TFRAG_TEX_LEVEL1, m_texture_animator); // 13 : TFRAG_LEVEL1 init_bucket_renderer("l1-tfrag-tfrag", BucketCategory::TFRAG, BucketId::TFRAG_LEVEL1, - normal_tfrags, false, 1); + normal_tfrags, false, 1, anim_slot_array()); // 14 : TFRAG_NEAR_LEVEL1 // 15 : TIE_NEAR_LEVEL1 // 16 : TIE_LEVEL1 @@ -430,14 +429,15 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { BucketId::ALPHA_TEX_LEVEL0, m_texture_animator); // 31 init_bucket_renderer("l0-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER, BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, 0, - sky_gpu_blender, sky_cpu_blender); // 32 + sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 32 // 33 init_bucket_renderer("l0-alpha-tfrag", BucketCategory::TFRAG, - BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false, - 0); // 34 + BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false, 0, + anim_slot_array()); // 34 // 35 init_bucket_renderer("l0-alpha-tfrag-ice", BucketCategory::TFRAG, - BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0); + BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0, + anim_slot_array()); // 37 //----------------------- @@ -447,14 +447,15 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { BucketId::ALPHA_TEX_LEVEL1, m_texture_animator); // 38 init_bucket_renderer("l1-alpha-sky-blend-and-tfrag-trans", BucketCategory::OTHER, BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, 1, - sky_gpu_blender, sky_cpu_blender); // 39 + sky_gpu_blender, sky_cpu_blender, anim_slot_array()); // 39 // 40 init_bucket_renderer("l1-alpha-tfrag-dirt", BucketCategory::TFRAG, - BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false, - 1); // 41 + BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false, 1, + anim_slot_array()); // 41 // 42 init_bucket_renderer("l1-alpha-tfrag-ice", BucketCategory::TFRAG, - BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1); + BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1, + anim_slot_array()); // 44 init_bucket_renderer("common-alpha-merc", BucketCategory::MERC, diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index ebacf6a7e8..a7475cf526 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -98,6 +98,10 @@ class OpenGLRenderer { return ret; } + const std::vector* anim_slot_array() { + return m_texture_animator ? m_texture_animator->slots() : nullptr; + } + SharedRenderState m_render_state; Profiler m_profiler; SmallProfiler m_small_profiler; diff --git a/game/graphics/opengl_renderer/SkyRenderer.cpp b/game/graphics/opengl_renderer/SkyRenderer.cpp index 61918f8637..855d8eee03 100644 --- a/game/graphics/opengl_renderer/SkyRenderer.cpp +++ b/game/graphics/opengl_renderer/SkyRenderer.cpp @@ -24,7 +24,8 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name, int my_id, int level_id, std::shared_ptr shared_blender, - std::shared_ptr shared_blender_cpu) + std::shared_ptr shared_blender_cpu, + const std::vector* anim_slots) : BucketRenderer(name, my_id), m_shared_gpu_blender(shared_blender), m_shared_cpu_blender(shared_blender_cpu), @@ -32,7 +33,8 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name, my_id, {tfrag3::TFragmentTreeKind::TRANS, tfrag3::TFragmentTreeKind::LOWRES_TRANS}, true, - level_id) {} + level_id, + anim_slots) {} void SkyBlendHandler::init_shaders(ShaderLibrary& shaders) { m_tfrag_renderer.init_shaders(shaders); diff --git a/game/graphics/opengl_renderer/SkyRenderer.h b/game/graphics/opengl_renderer/SkyRenderer.h index 7d7e5db0ab..839e7b2253 100644 --- a/game/graphics/opengl_renderer/SkyRenderer.h +++ b/game/graphics/opengl_renderer/SkyRenderer.h @@ -16,7 +16,8 @@ class SkyBlendHandler : public BucketRenderer { int my_id, int level_id, std::shared_ptr shared_gpu_blender, - std::shared_ptr shared_cpu_blender); + std::shared_ptr shared_cpu_blender, + const std::vector* anim_slots); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; void init_shaders(ShaderLibrary& shaders) override; diff --git a/game/graphics/opengl_renderer/TextureAnimator.cpp b/game/graphics/opengl_renderer/TextureAnimator.cpp index 3cf926962b..9e3e016c7c 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.cpp +++ b/game/graphics/opengl_renderer/TextureAnimator.cpp @@ -14,12 +14,39 @@ #define dprintf(...) #define dfmt(...) +// -- Texture Animations +// The game has a number of "texture animation arrays". +// On the original PS2, there wasn't enough VRAM to hold all textures for a frame, so they would +// upload a single "tpage" at a time. Along with this, they would dynamically generate some animated +// textures. Each tpage has an associated texture animation array. + +// -- Our approach +// This part of the code has turned out to be pretty bad in terms of performance. +// We also have the challenge of actually getting all the renderers to look at the right animated +// textures, which is tricky because we rewrote them for jak 1, which doesn't have this. +// +// So there's a lot of tricks here to try to speed things up. We modified the GOAL code to work +// better with this code. We have three different approaches to handling a texture animation array: +// - Emulation (slowest). This basically pretends to be the PS2, and is the most flexible. It reads +// the DMA and maps it to OpenGL operations right now it's used for the clouds (though slightly +// wrong). +// - Clut-blending. This special cases animations which are just blends between CLUTs. +// We optimize this by only doing work if the blend weights change (they didn't on PS2 because +// they don't have the vram to store the texture). We also avoid the use of render-to-texture. +// Jak's hair/skin/fingers always use this texture. +// - "Fixed Animation". For animations that use only basic features, we have a way to run them +// entirely in C++. This avoids repeated switches between framebuffers, and lets us precompute +// more stuff. + +// -- OpenGL performance. // So there's a lot of stupid-looking OpenGL stuff going on here. // The motivation for this is to avoid an issue where some operations take about 5-10ms. // As far as I can tell, this slow operation is actually the driver forcing this thread to sync // with some internal stuff. It seems to be triggered on: -// - deleting a texture that's in use -// - glTexImage2D to modify a texture with a different sized texture. +// - deleting a texture that was used on the previous frame (so in use by the driver thread). +// this is a "safe" operation, but I suspect it forces the driver thread to synchronize). +// - glTexImage2D to modify a texture with a different sized texture. (likely hits same case as +// above) // TODO: // clouds aren't really working right. The final operation of move rb to ba is a guess. @@ -35,12 +62,13 @@ OpenGLTexturePool::OpenGLTexturePool() { struct Alloc { u64 w, h, n; }; - // list of sizes to preallocate. + // list of sizes to preallocate: {width, height, count}. for (const auto& a : std::vector{{16, 16, 5}, // {32, 16, 1}, - {32, 32, 5}, + {32, 32, 8}, {32, 64, 1}, - {64, 64, 8}, + {64, 32, 4}, + {64, 64, 9}, {64, 128, 4}, {128, 128, 5}, {256, 1, 2}, @@ -62,6 +90,9 @@ OpenGLTexturePool::~OpenGLTexturePool() { } } +/*! + * Get a preallocated texture with the given size, or fatal error if we are out. + */ GLuint OpenGLTexturePool::allocate(u64 w, u64 h) { const auto& it = textures.find((w << 32) | h); if (it == textures.end()) { @@ -77,10 +108,19 @@ GLuint OpenGLTexturePool::allocate(u64 w, u64 h) { return ret; } +/*! + * Return a texture to the pool. The size must be provided. + */ void OpenGLTexturePool::free(GLuint texture, u64 w, u64 h) { textures[(w << 32) | h].push_back(texture); } +/*! + * Get an index-format texture from the given tfrag3 level. + * In cases where multiple original-game-levels both provide a texture, but the data is different, + * prefer the one from the given level. + * This is slow and intended to be used an init time. + */ const tfrag3::IndexTexture* itex_by_name(const tfrag3::Level* level, const std::string& name, const std::optional& level_name) { @@ -115,6 +155,36 @@ const tfrag3::IndexTexture* itex_by_name(const tfrag3::Level* level, return ret; } +/*! + * Get a RGBA format texture by name from the given tfrag3 level. Slow, and intended for init time. + */ +const tfrag3::Texture* tex_by_name(const tfrag3::Level* level, const std::string& name) { + const tfrag3::Texture* ret = nullptr; + for (const auto& t : level->textures) { + if (t.debug_name == name) { + if (ret) { + lg::error("Multiple textures named {}", name); + ASSERT(ret->data == t.data); + } + ret = &t; + } + } + if (!ret) { + lg::error("no texture named {}", name); + for (const auto& t : level->textures) { + fmt::print("texture: {}\n", t.debug_name); + } + lg::die("no texture named {}", name); + } else { + // lg::info("got idx: {}", name); + } + return ret; +} + +/*! + * Get a texture animation slot index for the given name. Fatal error if there is no animated + * texture slot with this name. Slow, and intended for init time. + */ int output_slot_by_idx(GameVersion version, const std::string& name) { const std::vector* v = nullptr; switch (version) { @@ -134,27 +204,52 @@ int output_slot_by_idx(GameVersion version, const std::string& name) { ASSERT_NOT_REACHED(); } +/*! + * Upload a texture and generate mipmaps. Assumes the usual RGBA format. + */ +void opengl_upload_texture(GLint dest, const void* data, int w, int h) { + glBindTexture(GL_TEXTURE_2D, dest); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data); + glGenerateMipmap(GL_TEXTURE_2D); + float aniso = 0.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso); + glBindTexture(GL_TEXTURE_2D, 0); +} + +/*! + * Utility class to grab CLUTs from the source textures, blend them, and produce a destination RGBA + * texture using the index data in dest. + */ ClutBlender::ClutBlender(const std::string& dest, const std::vector& sources, const std::optional& level_name, const tfrag3::Level* level, OpenGLTexturePool* tpool) { + // find the destination texture m_dest = itex_by_name(level, dest, level_name); + // find the clut source textures for (const auto& sname : sources) { m_cluts.push_back(&itex_by_name(level, sname, level_name)->color_table); m_current_weights.push_back(0); } + // opengl texture that we'll write to m_texture = tpool->allocate(m_dest->w, m_dest->h); m_temp_rgba.resize(m_dest->w * m_dest->h); + // default to the first one. std::vector init_weights(m_current_weights.size(), 0); init_weights.at(0) = 1.f; run(init_weights.data()); } +/*! + * Blend cluts and create an output texture. + */ GLuint ClutBlender::run(const float* weights) { bool needs_run = false; + // check if weights changed or not. for (size_t i = 0; i < m_current_weights.size(); i++) { if (weights[i] != m_current_weights[i]) { needs_run = true; @@ -166,10 +261,12 @@ GLuint ClutBlender::run(const float* weights) { return m_texture; } + // update weights for (size_t i = 0; i < m_current_weights.size(); i++) { m_current_weights[i] = weights[i]; } + // blend cluts for (int i = 0; i < 256; i++) { math::Vector4f v = math::Vector4f::zero(); for (size_t j = 0; j < m_current_weights.size(); j++) { @@ -178,22 +275,65 @@ GLuint ClutBlender::run(const float* weights) { m_temp_clut[i] = v.cast(); } + // do texture lookups for (int i = 0; i < m_temp_rgba.size(); i++) { memcpy(&m_temp_rgba[i], m_temp_clut[m_dest->index_data[i]].data(), 4); } - glBindTexture(GL_TEXTURE_2D, m_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_dest->w, m_dest->h, 0, GL_RGBA, - GL_UNSIGNED_INT_8_8_8_8_REV, m_temp_rgba.data()); - glGenerateMipmap(GL_TEXTURE_2D); - float aniso = 0.0f; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso); - glBindTexture(GL_TEXTURE_2D, 0); + // send to GPU. + opengl_upload_texture(m_texture, m_temp_rgba.data(), m_dest->w, m_dest->h); return m_texture; } +/*! + * Utility class to show what happens if you take a PSM32 texture, upload it as PSM32, then read it + * back as PSM8. Byte i from the input data ends up in destinations_per_byte[i]. + */ +Psm32ToPsm8Scrambler::Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width) { + struct InAddr { + int x = -1, y = -1, c = -1; + }; + struct OutAddr { + int x = -1, y = -1; + }; + + std::vector vram_from_in(w * h * 4); + std::vector vram_from_out(w * h * 4); + + // loop over pixels in input + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int byte_addr = psmct32_addr(x, y, write_tex_width); + for (int c = 0; c < 4; c++) { + auto& s = vram_from_in.at(byte_addr + c); + s.x = x; + s.y = y; + s.c = c; + } + } + } + + // output + for (int y = 0; y < h * 2; y++) { + for (int x = 0; x < w * 2; x++) { + int byte_addr = psmt8_addr(x, y, read_tex_width); + auto& s = vram_from_out.at(byte_addr); + s.x = x; + s.y = y; + } + } + + destinations_per_byte.resize(4 * w * h); + for (size_t i = 0; i < vram_from_out.size(); i++) { + auto& in = vram_from_in.at(i); + auto& out = vram_from_out.at(i); + if (in.c >= 0) { + destinations_per_byte.at(in.c + in.x * 4 + in.y * 4 * w) = out.x + out.y * w * 2; + } + } +} + TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level) : m_common_level(common_level), m_psm32_to_psm8_8_8(8, 8, 8, 64), @@ -232,15 +372,24 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co m_uniforms.uvs = glGetUniformLocation(shader.id(), "uvs"); m_uniforms.channel_scramble = glGetUniformLocation(shader.id(), "channel_scramble"); m_uniforms.tcc = glGetUniformLocation(shader.id(), "tcc"); + m_uniforms.alpha_multiply = glGetUniformLocation(shader.id(), "alpha_multiply"); // create a single "dummy texture" with all 0 data. // this is faster and easier than switching shaders to one without texturing, and is used // only rarely glGenTextures(1, &m_dummy_texture); glBindTexture(GL_TEXTURE_2D, m_dummy_texture); - std::vector data(16 * 16 * 4); + std::vector data(16 * 16); + u32 c0 = 0xa0303030; + u32 c1 = 0xa0e0e0e0; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + data[i * 16 + j] = (((i / 4) & 1) ^ ((j / 4) & 1)) ? c1 : c0; + } + } glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data.data()); + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); shader.activate(); @@ -262,51 +411,81 @@ TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* co m_index_to_clut_addr[i] = clx + cly * 16; } - m_output_slots.resize(jak2_animated_texture_slots().size(), m_dummy_texture); + m_public_output_slots.resize(jak2_animated_texture_slots().size(), m_dummy_texture); + m_private_output_slots = m_public_output_slots; + m_output_debug_flags.resize(jak2_animated_texture_slots().size()); - // DARKJAK - m_darkjak_clut_blender_idx = create_clut_blender_group( - {"jakbsmall-eyebrow", "jakbsmall-face", "jakbsmall-finger", "jakbsmall-hair"}, "-norm", - "-dark", {}); + // animation-specific stuff + setup_texture_anims(); +} - // PRISON - // MISSING EYELID - m_jakb_prison_clut_blender_idx = create_clut_blender_group( - {"jak-orig-arm-formorph", "jak-orig-eyebrow-formorph", "jak-orig-finger-formorph"}, "-start", - "-end", "LDJAKBRN.DGO"); - add_to_clut_blender_group(m_jakb_prison_clut_blender_idx, - {"jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", "-dark", - "LDJAKBRN.DGO"); +/*! + * Add a fixed texture animator for the given definition. Returns an index that can later be used to + * run it. + */ +int TextureAnimator::create_fixed_anim_array(const std::vector& defs) { + int ret = m_fixed_anim_arrays.size(); + auto& anim_array = m_fixed_anim_arrays.emplace_back(); - // ORACLE - // MISSING FINGER - m_jakb_oracle_clut_blender_idx = create_clut_blender_group( - {"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", - "-dark", "ORACLE.DGO"); + for (const auto& def : defs) { + auto& anim = anim_array.anims.emplace_back(); + anim.def = def; - // NEST - // MISSING FINGER - m_jakb_nest_clut_blender_idx = create_clut_blender_group( - {"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", - "-dark", "NEB.DGO"); + // set up the destination texture. + anim.dest_slot = output_slot_by_idx(GameVersion::Jak2, anim.def.tex_name); + auto* dtex = tex_by_name(m_common_level, anim.def.tex_name); + if (anim.def.override_size) { + anim.fbt.emplace(anim.def.override_size->x(), anim.def.override_size->y(), + GL_UNSIGNED_INT_8_8_8_8_REV); + } else { + anim.fbt.emplace(dtex->w, dtex->h, GL_UNSIGNED_INT_8_8_8_8_REV); + opengl_upload_texture(anim.fbt->texture(), dtex->data.data(), dtex->w, dtex->h); + } - // KOR (doesn't work??) - m_kor_transform_clut_blender_idx = create_clut_blender_group( - { - // "kor-eyeeffect-formorph", - // "kor-hair-formorph", - // "kor-head-formorph", - // "kor-head-formorph-noreflect", - // "kor-lowercaps-formorph", - // "kor-uppercaps-formorph", - }, - "-start", "-end", {}); + m_private_output_slots.at(anim.dest_slot) = anim.fbt->texture(); + + // set up the source textures + for (const auto& layer : def.layers) { + auto* stex = tex_by_name(m_common_level, layer.tex_name); + GLint gl_texture = m_opengl_texture_pool.allocate(stex->w, stex->h); + anim.src_textures.push_back(gl_texture); + opengl_upload_texture(gl_texture, stex->data.data(), stex->w, stex->h); + } + } + + return ret; } void TextureAnimator::draw_debug_window() { ImGui::Checkbox("fast-scrambler", &m_debug.use_fast_scrambler); + + auto& slots = jak2_animated_texture_slots(); + for (size_t i = 0; i < slots.size(); i++) { + ImGui::Text("Slot %d %s", (int)i, slots[i].c_str()); + glBindTexture(GL_TEXTURE_2D, m_private_output_slots[i]); + int w, h; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); + ImGui::Image((void*)m_private_output_slots[i], ImVec2(w, h)); + ImGui::Checkbox(fmt::format("mark {}", i).c_str(), &m_output_debug_flags.at(i).b); + } + glBindTexture(GL_TEXTURE_2D, 0); } +void TextureAnimator::copy_private_to_public() { + auto& slots = jak2_animated_texture_slots(); + for (size_t i = 0; i < slots.size(); i++) { + if (m_output_debug_flags[i].b) { + m_public_output_slots[i] = m_dummy_texture; + } else { + m_public_output_slots[i] = m_private_output_slots[i]; + } + } +} + +/*! + * Create a clut-blending animator. Returns an index that can later be used to run it. + */ int TextureAnimator::create_clut_blender_group(const std::vector& textures, const std::string& suffix0, const std::string& suffix1, @@ -317,6 +496,9 @@ int TextureAnimator::create_clut_blender_group(const std::vector& t return ret; } +/*! + * Add a texture to an existing blender group created with create_clut_blender_group. + */ void TextureAnimator::add_to_clut_blender_group(int idx, const std::vector& textures, const std::string& suffix0, @@ -327,7 +509,7 @@ void TextureAnimator::add_to_clut_blender_group(int idx, grp.blenders.emplace_back(prefix, std::vector{prefix + suffix0, prefix + suffix1}, dgo, m_common_level, &m_opengl_texture_pool); grp.outputs.push_back(output_slot_by_idx(GameVersion::Jak2, prefix)); - m_output_slots.at(grp.outputs.back()) = grp.blenders.back().texture(); + m_private_output_slots.at(grp.outputs.back()) = grp.blenders.back().texture(); } } @@ -338,8 +520,8 @@ TextureAnimator::~TextureAnimator() { } GLuint TextureAnimator::get_by_slot(int idx) { - ASSERT(idx >= 0 && idx < (int)m_output_slots.size()); - return m_output_slots[idx]; + ASSERT(idx >= 0 && idx < (int)m_public_output_slots.size()); + return m_public_output_slots[idx]; } // IDs sent from GOAL telling us what texture operation to perform. @@ -357,7 +539,10 @@ enum PcTextureAnimCodes { PRISON_JAK = 23, ORACLE_JAK = 24, NEST_JAK = 25, - KOR_TRANSFORM = 26 + KOR_TRANSFORM = 26, + SKULL_GEM = 27, + BOMB = 28, + CAS_CONVEYOR = 29, }; // metadata for an upload from GOAL memory @@ -466,6 +651,22 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma, auto p = scoped_prof("kor"); run_clut_blender_group(tf, m_kor_transform_clut_blender_idx); } break; + case SKULL_GEM: { + auto p = scoped_prof("skull-gem"); + ASSERT(tf.size_bytes == 16); + const float* floats = (const float*)tf.data; + run_fixed_animation_array(m_skull_gem_fixed_anim_array_idx, floats); + } break; + case BOMB: { + auto p = scoped_prof("bomb"); + ASSERT(tf.size_bytes == 16); + const float* floats = (const float*)tf.data; + run_fixed_animation_array(m_bomb_fixed_anim_array_idx, floats); + } break; + case CAS_CONVEYOR: { + auto p = scoped_prof("cas-conveyor"); + run_fixed_animation_array(m_cas_conveyor_anim_array_idx, (const float*)tf.data); + } break; default: fmt::print("bad imm: {}\n", vif0.immediate); ASSERT_NOT_REACHED(); @@ -541,6 +742,7 @@ void TextureAnimator::handle_texture_anim_data(DmaFollower& dma, glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); glColorMask(true, true, true, true); + copy_private_to_public(); } /*! @@ -644,6 +846,8 @@ void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) { glUniform1i(m_uniforms.enable_tex, 1); glUniform4f(m_uniforms.rgba, 256, 256, 256, 128); // TODO - seems wrong. glUniform4i(m_uniforms.channel_scramble, 0, 1, 0, 1); + // not sure if this is right or not: the entire cloud stuff is kinda broken. + glUniform1f(m_uniforms.alpha_multiply, 1.f); glBindTexture(GL_TEXTURE_2D, src->second.tex.value().texture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -679,7 +883,9 @@ void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) { } void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) { - ASSERT_NOT_REACHED(); + ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff + glUniform1f(m_uniforms.alpha_multiply, 1.f); + dprintf("[tex anim] set clut alpha\n"); ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform)); auto* data = (const TextureAnimPcTransform*)(tf.data); @@ -707,7 +913,9 @@ void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) { } void TextureAnimator::handle_copy_clut_alpha(const DmaTransfer& tf) { - ASSERT_NOT_REACHED(); + ASSERT_NOT_REACHED(); // TODO: if re-enabling, needs alpha multiplier stuff + glUniform1f(m_uniforms.alpha_multiply, 1.f); + dprintf("[tex anim] __copy__ clut alpha\n"); ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform)); auto* data = (const TextureAnimPcTransform*)(tf.data); @@ -749,7 +957,7 @@ void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx) { float weights[2] = {1.f - f, f}; auto& blender = m_clut_blender_groups.at(idx); for (size_t i = 0; i < blender.blenders.size(); i++) { - m_output_slots[blender.outputs[i]] = blender.blenders[i].run(weights); + m_private_output_slots[blender.outputs[i]] = blender.blenders[i].run(weights); } } @@ -899,6 +1107,8 @@ void TextureAnimator::handle_erase_dest(DmaFollower& dma) { glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glColorMask(true, true, true, true); + // write the exact specified alpha (texture holds game-style alphas) + glUniform1f(m_uniforms.alpha_multiply, 1.f); { auto p = scoped_prof("erase-draw"); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); @@ -1046,14 +1256,27 @@ void TextureAnimator::handle_draw(DmaFollower& dma, TexturePool& texture_pool) { } // use ADGIF shader data to set OpenGL state - set_up_opengl_for_shader(m_current_shader, gpu_texture, true); // ABE forced on here. + bool writes_alpha = + set_up_opengl_for_shader(m_current_shader, gpu_texture, true); // ABE forced on here. // set up uniform buffers for the coordinates for this draw. set_uniforms_from_draw_data(draw_data, dest_te.tex_width, dest_te.tex_height); ASSERT(dest_te.tex); - // draw! - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + if (writes_alpha) { + glColorMask(true, true, true, false); + glUniform1f(m_uniforms.alpha_multiply, 2.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glColorMask(false, false, false, true); + glUniform1f(m_uniforms.alpha_multiply, 1.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } else { + // we don't write alpha out. So apply alpha multiplier for blending. + glUniform1f(m_uniforms.alpha_multiply, 1.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + // debug_save_opengl_texture("opengl_draw_result.png", dest_te.tex->texture()); // debug_save_opengl_texture("opengl_test.png", gpu_texture); } else { @@ -1226,7 +1449,75 @@ GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool& } } -void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader, +void TextureAnimator::set_up_opengl_for_fixed(const FixedLayerDef& def, + std::optional texture) { + if (texture) { + glBindTexture(GL_TEXTURE_2D, *texture); + glUniform1i(m_uniforms.enable_tex, 1); + } else { + glBindTexture(GL_TEXTURE_2D, m_dummy_texture); + glUniform1i(m_uniforms.enable_tex, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + // tex0 + // assuming default-texture-anim-layer-func, which sets 1. + glUniform1i(m_uniforms.tcc, 1); + + // ASSERT(shader.tex0.tfx() == GsTex0::TextureFunction::MODULATE); + // tex1 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glColorMask(def.channel_masks[0], def.channel_masks[1], def.channel_masks[2], + def.channel_masks[3]); + if (def.z_test) { + ASSERT_NOT_REACHED(); + } else { + glDisable(GL_DEPTH_TEST); + } + + if (def.clamp_u) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + } + + if (def.clamp_v) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + if (def.blend_enable) { + auto blend_a = def.blend_modes[0]; + auto blend_b = def.blend_modes[1]; + auto blend_c = def.blend_modes[2]; + auto blend_d = def.blend_modes[3]; + glEnable(GL_BLEND); + + // 0 2 0 1 + if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::ZERO_OR_FIXED && + blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) { + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO); + } else if (blend_a == GsAlpha::BlendMode::SOURCE && blend_b == GsAlpha::BlendMode::DEST && + blend_c == GsAlpha::BlendMode::SOURCE && blend_d == GsAlpha::BlendMode::DEST) { + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + } else { + fmt::print("unhandled blend: {} {} {} {}\n", (int)blend_a, (int)blend_b, (int)blend_c, + (int)blend_d); + ASSERT_NOT_REACHED(); + } + + } else { + glDisable(GL_BLEND); + } + glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3); +} + +bool TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader, std::optional texture, bool prim_abe) { if (texture) { @@ -1290,7 +1581,9 @@ void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader, do_alpha_test = false; } + bool writes_alpha = true; if (alpha_test_mask_alpha_trick) { + writes_alpha = false; glColorMask(true, true, true, false); } else { glColorMask(true, true, true, true); @@ -1351,6 +1644,7 @@ void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader, glDisable(GL_BLEND); } glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3); + return writes_alpha; } namespace { @@ -1394,3 +1688,367 @@ void TextureAnimator::set_uniforms_from_draw_data(const DrawData& dd, int dest_w // fmt::print("fan vt {}: {:.3f} {:.3f} \n", i, uv[i * 2], uv[1 + i * 2]); // } } + +void TextureAnimator::run_fixed_animation_array(int idx, const float* times) { + auto& array = m_fixed_anim_arrays.at(idx); + for (size_t i = 0; i < array.anims.size(); i++) { + auto& anim = array.anims[i]; + run_fixed_animation(anim, times[i]); + } +} + +template +void interpolate_1(float interp, T* out, const T& in_start, const T& in_end) { + *out = in_start + (in_end - in_start) * interp; +} + +void interpolate_layer_values(float interp, + LayerVals* out, + const LayerVals& start, + const LayerVals& end) { + interpolate_1(interp, &out->color, start.color, end.color); + interpolate_1(interp, &out->scale, start.scale, end.scale); + interpolate_1(interp, &out->offset, start.offset, end.offset); + interpolate_1(interp, &out->st_scale, start.st_scale, end.st_scale); + interpolate_1(interp, &out->st_offset, start.st_offset, end.st_offset); + interpolate_1(interp, &out->qs, start.qs, end.qs); +} + +void TextureAnimator::set_draw_data_from_interpolated(DrawData* result, + const LayerVals& vals, + int w, + int h) { + result->color = (vals.color * 128.f).cast(); + math::Vector2f pos_scale(vals.scale.x() * w, vals.scale.y() * h); + math::Vector2f pos_offset(2048.f + (vals.offset.x() * w), 2048.f + (vals.offset.y() * h)); + math::Vector2f st_scale = vals.st_scale; + math::Vector2f st_offset = vals.st_offset; + const math::Vector2f corners[4] = {math::Vector2f{-0.5, -0.5}, math::Vector2f{0.5, -0.5}, + math::Vector2f{-0.5, 0.5}, math::Vector2f{0.5, 0.5}}; + math::Vector2f sts[4]; + math::Vector2 poss[4]; + + for (int i = 0; i < 4; i++) { + sts[i] = corners[i].elementwise_multiply(st_scale) + st_offset; + poss[i] = ((corners[i].elementwise_multiply(pos_scale) + pos_offset) * 16.f).cast(); + } + + result->st0.x() = sts[0].x(); + result->st0.y() = sts[0].y(); + result->st1.x() = sts[1].x(); + result->st1.y() = sts[1].y(); + result->st2.x() = sts[2].x(); + result->st2.y() = sts[2].y(); + result->st3.x() = sts[3].x(); + result->st3.y() = sts[3].y(); + + result->pos0.x() = poss[0].x(); + result->pos0.y() = poss[0].y(); + result->pos1.x() = poss[1].x(); + result->pos1.y() = poss[1].y(); + result->pos2.x() = poss[2].x(); + result->pos2.y() = poss[2].y(); + result->pos3.x() = poss[3].x(); + result->pos3.y() = poss[3].y(); +} + +void TextureAnimator::run_fixed_animation(FixedAnim& anim, float time) { + { + FramebufferTexturePairContext ctxt(anim.fbt.value()); + // Clear + { + float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + glUniform3fv(m_uniforms.positions, 4, positions); + glUniform1i(m_uniforms.enable_tex, 0); + glUniform4f(m_uniforms.rgba, anim.def.color[0], anim.def.color[1], anim.def.color[2], + anim.def.color[3]); + glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3); + glBindTexture(GL_TEXTURE_2D, m_dummy_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glColorMask(true, true, true, true); + glUniform1f(m_uniforms.alpha_multiply, 1.f); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + + LayerVals interpolated_values; + DrawData draw_data; + + // Loop over layers + for (size_t layer_idx = 0; layer_idx < anim.def.layers.size(); layer_idx++) { + auto& layer_def = anim.def.layers[layer_idx]; + // skip layer if out the range when it is active + if (time < layer_def.start_time || time > layer_def.end_time) { + continue; + } + + // interpolate + interpolate_layer_values( + (time - layer_def.start_time) / (layer_def.end_time - layer_def.start_time), + &interpolated_values, layer_def.start_vals, layer_def.end_vals); + + // shader setup + set_up_opengl_for_fixed(layer_def, anim.src_textures.at(layer_idx)); + + set_draw_data_from_interpolated(&draw_data, interpolated_values, anim.fbt->width(), + anim.fbt->height()); + set_uniforms_from_draw_data(draw_data, anim.fbt->width(), anim.fbt->height()); + + if (true) { // todo + glColorMask(true, true, true, false); + glUniform1f(m_uniforms.alpha_multiply, 2.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glColorMask(false, false, false, true); + glUniform1f(m_uniforms.alpha_multiply, 1.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } else { + // we don't write alpha out. So apply alpha multiplier for blending. + glUniform1f(m_uniforms.alpha_multiply, 2.f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + } + // Finish + m_private_output_slots.at(anim.dest_slot) = anim.fbt->texture(); + glBindTexture(GL_TEXTURE_2D, anim.fbt->texture()); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void TextureAnimator::setup_texture_anims() { + // DARKJAK + m_darkjak_clut_blender_idx = create_clut_blender_group( + {"jakbsmall-eyebrow", "jakbsmall-face", "jakbsmall-finger", "jakbsmall-hair"}, "-norm", + "-dark", {}); + + // PRISON + // MISSING EYELID + m_jakb_prison_clut_blender_idx = create_clut_blender_group( + {"jak-orig-arm-formorph", "jak-orig-eyebrow-formorph", "jak-orig-finger-formorph"}, "-start", + "-end", "LDJAKBRN.DGO"); + add_to_clut_blender_group(m_jakb_prison_clut_blender_idx, + {"jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", "-dark", + "LDJAKBRN.DGO"); + + // ORACLE + // MISSING FINGER + m_jakb_oracle_clut_blender_idx = create_clut_blender_group( + {"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", + "-dark", "ORACLE.DGO"); + + // NEST + // MISSING FINGER + m_jakb_nest_clut_blender_idx = create_clut_blender_group( + {"jakb-eyebrow", "jakb-eyelid", "jakb-facelft", "jakb-facert", "jakb-hairtrans"}, "-norm", + "-dark", "NEB.DGO"); + + // KOR (doesn't work??) + m_kor_transform_clut_blender_idx = create_clut_blender_group( + { + // "kor-eyeeffect-formorph", + // "kor-hair-formorph", + // "kor-head-formorph", + // "kor-head-formorph-noreflect", + // "kor-lowercaps-formorph", + // "kor-uppercaps-formorph", + }, + "-start", "-end", {}); + + // Skull Gem + { + FixedAnimDef skull_gem; + skull_gem.tex_name = "skull-gem-dest"; + skull_gem.color = math::Vector4{0, 0, 0, 0x80}; + + auto& skull_gem_0 = skull_gem.layers.emplace_back(); + skull_gem_0.end_time = 300.; + skull_gem_0.tex_name = "skull-gem-alpha-00"; + skull_gem_0.set_blend_b2_d1(); + skull_gem_0.set_no_z_write_no_z_test(); + skull_gem_0.start_vals.color = math::Vector4f{1, 1, 1, 1}; + skull_gem_0.start_vals.scale = math::Vector2f{1, 1}; + skull_gem_0.start_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_0.start_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_0.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + skull_gem_0.end_vals.color = math::Vector4f{1, 1, 1, 1}; + skull_gem_0.end_vals.scale = math::Vector2f{1, 1}; + skull_gem_0.end_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_0.end_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_0.end_vals.st_offset = math::Vector2f{0.5, 1.0}; + + auto& skull_gem_1 = skull_gem.layers.emplace_back(); + skull_gem_1.end_time = 300.; + skull_gem_1.tex_name = "skull-gem-alpha-01"; + skull_gem_1.set_blend_b2_d1(); + skull_gem_1.set_no_z_write_no_z_test(); + skull_gem_1.start_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1}; + skull_gem_1.start_vals.scale = math::Vector2f{1, 1}; + skull_gem_1.start_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_1.start_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_1.start_vals.st_offset = math::Vector2f{2.0, 1.0}; + skull_gem_1.end_vals.color = math::Vector4f{0.6, 0.5, 0.6, 1}; + skull_gem_1.end_vals.scale = math::Vector2f{1, 1}; + skull_gem_1.end_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_1.end_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_1.end_vals.st_offset = math::Vector2f{0, 0}; + + auto& skull_gem_2 = skull_gem.layers.emplace_back(); + skull_gem_2.end_time = 300.; + skull_gem_2.tex_name = "skull-gem-alpha-02"; + skull_gem_2.set_blend_b2_d1(); + skull_gem_2.set_no_z_write_no_z_test(); + skull_gem_2.start_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1}; + skull_gem_2.start_vals.scale = math::Vector2f{1, 1}; + skull_gem_2.start_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_2.start_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_2.start_vals.st_offset = math::Vector2f{0, 0}; + skull_gem_2.end_vals.color = math::Vector4f{0.6, 0.6, 0.6, 1}; + skull_gem_2.end_vals.scale = math::Vector2f{1, 1}; + skull_gem_2.end_vals.offset = math::Vector2f{0.5, 0.5}; + skull_gem_2.end_vals.st_scale = math::Vector2f{1, 1}; + skull_gem_2.end_vals.st_offset = math::Vector2f{2, 2}; + + m_skull_gem_fixed_anim_array_idx = create_fixed_anim_array({skull_gem}); + } + + // Bomb + { + FixedAnimDef bomb; + bomb.tex_name = "bomb-gradient"; + bomb.color = math::Vector4{0, 0, 0, 0x80}; + + auto& bomb_0 = bomb.layers.emplace_back(); + bomb_0.end_time = 300.; + bomb_0.tex_name = "bomb-gradient-rim"; + bomb_0.set_blend_b2_d1(); + bomb_0.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + bomb_0.channel_masks[3] = false; // no alpha writes. + bomb_0.start_vals.color = math::Vector4f{1, 1, 1, 1}; + bomb_0.start_vals.scale = math::Vector2f{1, 1}; + bomb_0.start_vals.offset = math::Vector2f{0.5, 0.5}; + bomb_0.start_vals.st_scale = math::Vector2f{1, 1}; + bomb_0.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + bomb_0.end_vals.color = math::Vector4f{1, 1, 1, 1}; + bomb_0.end_vals.scale = math::Vector2f{1, 1}; + bomb_0.end_vals.offset = math::Vector2f{0.5, 0.5}; + bomb_0.end_vals.st_scale = math::Vector2f{1, 1}; + bomb_0.end_vals.st_offset = math::Vector2f{0.5, 4.0}; + + auto& bomb_1 = bomb.layers.emplace_back(); + bomb_1.end_time = 300.; + bomb_1.tex_name = "bomb-gradient-flames"; + bomb_1.set_blend_b2_d1(); + bomb_1.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + bomb_1.channel_masks[3] = false; // no alpha writes. + bomb_1.start_vals.color = math::Vector4f{1, 1, 1, 1}; + bomb_1.start_vals.scale = math::Vector2f{1, 1}; + bomb_1.start_vals.offset = math::Vector2f{0.5, 0.5}; + bomb_1.start_vals.st_scale = math::Vector2f{1, 1}; + bomb_1.start_vals.st_offset = math::Vector2f{0.0, 0.5}; + bomb_1.end_vals.color = math::Vector4f{1, 1, 1, 1}; + bomb_1.end_vals.scale = math::Vector2f{1, 1}; + bomb_1.end_vals.offset = math::Vector2f{0.5, 0.5}; + bomb_1.end_vals.st_scale = math::Vector2f{1, 1}; + bomb_1.end_vals.st_offset = math::Vector2f{-6.0, 0.5}; + + m_bomb_fixed_anim_array_idx = create_fixed_anim_array({bomb}); + } + + // CAS conveyor + { + FixedAnimDef conveyor_0; + conveyor_0.tex_name = "cas-conveyor-dest"; + conveyor_0.color = math::Vector4(0, 0, 0, 0x80); + conveyor_0.override_size = math::Vector2(64, 32); + auto& c0 = conveyor_0.layers.emplace_back(); + c0.set_blend_b2_d1(); + c0.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + c0.channel_masks[3] = false; // no alpha writes. + c0.end_time = 300.; + c0.tex_name = "cas-conveyor"; + c0.start_vals.color = math::Vector4f{1, 1, 1, 1}; + c0.start_vals.scale = math::Vector2f{1, 1}; + c0.start_vals.offset = math::Vector2f{0.5, 0.5}; + c0.start_vals.st_scale = math::Vector2f{1, 1}; + c0.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + c0.end_vals.color = math::Vector4f{1, 1, 1, 1}; + c0.end_vals.scale = math::Vector2f{1, 1}; + c0.end_vals.offset = math::Vector2f{0.5, 0.5}; + c0.end_vals.st_scale = math::Vector2f{1, 1}; + c0.end_vals.st_offset = math::Vector2f{0.5, 1.0}; + + FixedAnimDef conveyor_1; + conveyor_1.tex_name = "cas-conveyor-dest-01"; + conveyor_1.color = math::Vector4(0, 0, 0, 0x80); + conveyor_1.override_size = math::Vector2(64, 32); + auto& c1 = conveyor_1.layers.emplace_back(); + c1.set_blend_b2_d1(); + c1.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + c1.channel_masks[3] = false; // no alpha writes. + c1.end_time = 300.; + c1.tex_name = "cas-conveyor"; + c1.start_vals.color = math::Vector4f{1, 1, 1, 1}; + c1.start_vals.scale = math::Vector2f{1, 1}; + c1.start_vals.offset = math::Vector2f{0.5, 0.5}; + c1.start_vals.st_scale = math::Vector2f{1, 1}; + c1.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + c1.end_vals.color = math::Vector4f{1, 1, 1, 1}; + c1.end_vals.scale = math::Vector2f{1, 1}; + c1.end_vals.offset = math::Vector2f{0.5, 0.5}; + c1.end_vals.st_scale = math::Vector2f{1, 1}; + c1.end_vals.st_offset = math::Vector2f{0.5, 1.0}; + + FixedAnimDef conveyor_2; + conveyor_2.tex_name = "cas-conveyor-dest-02"; + conveyor_2.color = math::Vector4(0, 0, 0, 0x80); + conveyor_2.override_size = math::Vector2(64, 32); + auto& c2 = conveyor_2.layers.emplace_back(); + c2.set_blend_b2_d1(); + c2.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + c2.channel_masks[3] = false; // no alpha writes. + c2.end_time = 300.; + c2.tex_name = "cas-conveyor"; + c2.start_vals.color = math::Vector4f{1, 1, 1, 1}; + c2.start_vals.scale = math::Vector2f{1, 1}; + c2.start_vals.offset = math::Vector2f{0.5, 0.5}; + c2.start_vals.st_scale = math::Vector2f{1, 1}; + c2.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + c2.end_vals.color = math::Vector4f{1, 1, 1, 1}; + c2.end_vals.scale = math::Vector2f{1, 1}; + c2.end_vals.offset = math::Vector2f{0.5, 0.5}; + c2.end_vals.st_scale = math::Vector2f{1, 1}; + c2.end_vals.st_offset = math::Vector2f{0.5, 1.0}; + + FixedAnimDef conveyor_3; + conveyor_3.tex_name = "cas-conveyor-dest-03"; + conveyor_3.color = math::Vector4(0, 0, 0, 0x80); + conveyor_3.override_size = math::Vector2(64, 32); + auto& c3 = conveyor_3.layers.emplace_back(); + c3.set_blend_b2_d1(); + c3.set_no_z_write_no_z_test(); + // :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + c3.channel_masks[3] = false; // no alpha writes. + c3.end_time = 300.; + c3.tex_name = "cas-conveyor"; + c3.start_vals.color = math::Vector4f{1, 1, 1, 1}; + c3.start_vals.scale = math::Vector2f{1, 1}; + c3.start_vals.offset = math::Vector2f{0.5, 0.5}; + c3.start_vals.st_scale = math::Vector2f{1, 1}; + c3.start_vals.st_offset = math::Vector2f{0.5, 0.0}; + c3.end_vals.color = math::Vector4f{1, 1, 1, 1}; + c3.end_vals.scale = math::Vector2f{1, 1}; + c3.end_vals.offset = math::Vector2f{0.5, 0.5}; + c3.end_vals.st_scale = math::Vector2f{1, 1}; + c3.end_vals.st_offset = math::Vector2f{0.5, 1.0}; + + m_cas_conveyor_anim_array_idx = + create_fixed_anim_array({conveyor_0, conveyor_1, conveyor_2, conveyor_3}); + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/TextureAnimator.h b/game/graphics/opengl_renderer/TextureAnimator.h index 24af3bde76..73e9e43f3f 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.h +++ b/game/graphics/opengl_renderer/TextureAnimator.h @@ -82,50 +82,7 @@ class ClutBlender { }; struct Psm32ToPsm8Scrambler { - Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width) { - struct InAddr { - int x = -1, y = -1, c = -1; - }; - struct OutAddr { - int x = -1, y = -1; - }; - - std::vector vram_from_in(w * h * 4); - std::vector vram_from_out(w * h * 4); - - // loop over pixels in input - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - int byte_addr = psmct32_addr(x, y, write_tex_width); - for (int c = 0; c < 4; c++) { - auto& s = vram_from_in.at(byte_addr + c); - s.x = x; - s.y = y; - s.c = c; - } - } - } - - // output - for (int y = 0; y < h * 2; y++) { - for (int x = 0; x < w * 2; x++) { - int byte_addr = psmt8_addr(x, y, read_tex_width); - auto& s = vram_from_out.at(byte_addr); - s.x = x; - s.y = y; - } - } - - destinations_per_byte.resize(4 * w * h); - for (size_t i = 0; i < vram_from_out.size(); i++) { - auto& in = vram_from_in.at(i); - auto& out = vram_from_out.at(i); - if (in.c >= 0) { - destinations_per_byte.at(in.c + in.x * 4 + in.y * 4 * w) = out.x + out.y * w * 2; - } - } - } - + Psm32ToPsm8Scrambler(int w, int h, int write_tex_width, int read_tex_width); std::vector destinations_per_byte; }; @@ -154,6 +111,70 @@ struct ClutReader { } }; +struct LayerVals { + math::Vector4f color = math::Vector4f::zero(); + math::Vector2f scale = math::Vector2f::zero(); + math::Vector2f offset = math::Vector2f::zero(); + math::Vector2f st_scale = math::Vector2f::zero(); + math::Vector2f st_offset = math::Vector2f::zero(); + math::Vector4f qs = math::Vector4f(1, 1, 1, 1); +}; + +/*! + * A single layer in a FixedAnimationDef. + */ +struct FixedLayerDef { + enum class Kind { + DEFAULT_ANIM_LAYER, + } kind = Kind::DEFAULT_ANIM_LAYER; + float start_time = 0; + float end_time = 0; + std::string tex_name; + bool z_writes = false; + bool z_test = false; + bool clamp_u = false; + bool clamp_v = false; + bool blend_enable = true; + bool channel_masks[4] = {true, true, true, true}; + GsAlpha::BlendMode blend_modes[4]; // abcd + u8 blend_fix = 0; + LayerVals start_vals, end_vals; + + void set_blend_b2_d1() { + blend_modes[0] = GsAlpha::BlendMode::SOURCE; + blend_modes[1] = GsAlpha::BlendMode::ZERO_OR_FIXED; + blend_modes[2] = GsAlpha::BlendMode::SOURCE; + blend_modes[3] = GsAlpha::BlendMode::DEST; + blend_fix = 0; + } + + void set_no_z_write_no_z_test() { + z_writes = false; + z_test = false; + } +}; + +struct FixedAnimDef { + math::Vector4 color; // clear color + std::string tex_name; + std::optional> override_size; + // assuming (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + // alpha blend off, so alpha doesn't matter i think. + std::vector layers; +}; + +struct FixedAnim { + FixedAnimDef def; + // GLint dest_texture; + std::optional fbt; + int dest_slot; + std::vector src_textures; +}; + +struct FixedAnimArray { + std::vector anims; +}; + class TexturePool; class TextureAnimator { @@ -163,9 +184,11 @@ class TextureAnimator { void handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, TexturePool* texture_pool); GLuint get_by_slot(int idx); void draw_debug_window(); - const std::vector* slots() { return &m_output_slots; } + const std::vector* slots() { return &m_public_output_slots; } private: + void copy_private_to_public(); + void setup_texture_anims(); void handle_upload_clut_16_16(const DmaTransfer& tf, const u8* ee_mem); void handle_generic_upload(const DmaTransfer& tf, const u8* ee_mem); void handle_erase_dest(DmaFollower& dma); @@ -177,9 +200,10 @@ class TextureAnimator { VramEntry* setup_vram_entry_for_gpu_texture(int w, int h, int tbp); - void set_up_opengl_for_shader(const ShaderContext& shader, + bool set_up_opengl_for_shader(const ShaderContext& shader, std::optional texture, bool prim_abe); + void set_up_opengl_for_fixed(const FixedLayerDef& def, std::optional texture); void load_clut_to_converter(); const u32* get_clut_16_16_psm32(int cbp); @@ -189,6 +213,10 @@ class TextureAnimator { GLuint make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool); void force_to_gpu(int tbp); + int create_fixed_anim_array(const std::vector& defs); + void run_fixed_animation_array(int idx, const float* times); + void run_fixed_animation(FixedAnim& anim, float time); + struct DrawData { u8 tmpl1[16]; math::Vector color; @@ -207,6 +235,7 @@ class TextureAnimator { }; void set_uniforms_from_draw_data(const DrawData& dd, int dest_w, int dest_h); + void set_draw_data_from_interpolated(DrawData* result, const LayerVals& vals, int w, int h); PcTextureId get_id_for_tbp(TexturePool* pool, u32 tbp); @@ -243,6 +272,7 @@ class TextureAnimator { GLuint enable_tex; GLuint channel_scramble; GLuint tcc; + GLuint alpha_multiply; } m_uniforms; struct { @@ -255,7 +285,13 @@ class TextureAnimator { u8 m_index_to_clut_addr[256]; OpenGLTexturePool m_opengl_texture_pool; - std::vector m_output_slots; + std::vector m_private_output_slots; + std::vector m_public_output_slots; + + struct Bool { + bool b = false; + }; + std::vector m_output_debug_flags; struct ClutBlenderGroup { std::vector blenders; @@ -283,4 +319,9 @@ class TextureAnimator { Psm32ToPsm8Scrambler m_psm32_to_psm8_8_8, m_psm32_to_psm8_16_16, m_psm32_to_psm8_32_32, m_psm32_to_psm8_64_64; ClutReader m_clut_table; + + int m_skull_gem_fixed_anim_array_idx = -1; + int m_bomb_fixed_anim_array_idx = -1; + int m_cas_conveyor_anim_array_idx = -1; + std::vector m_fixed_anim_arrays; }; diff --git a/game/graphics/opengl_renderer/background/TFragment.cpp b/game/graphics/opengl_renderer/background/TFragment.cpp index 4cc4d8de53..9e5079119a 100644 --- a/game/graphics/opengl_renderer/background/TFragment.cpp +++ b/game/graphics/opengl_renderer/background/TFragment.cpp @@ -20,16 +20,52 @@ TFragment::TFragment(const std::string& name, int my_id, const std::vector& trees, bool child_mode, - int level_id) + int level_id, + const std::vector* anim_slot_array) : BucketRenderer(name, my_id), m_child_mode(child_mode), m_tree_kinds(trees), - m_level_id(level_id) { + m_level_id(level_id), + m_anim_slot_array(anim_slot_array) { for (auto& buf : m_buffered_data) { for (auto& x : buf.pad) { x = 0xff; } } + + glGenVertexArrays(1, &m_debug_vao); + glBindVertexArray(m_debug_vao); + glGenBuffers(1, &m_debug_verts); + glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts); + glBufferData(GL_ARRAY_BUFFER, DEBUG_TRI_COUNT * 3 * sizeof(DebugVertex), nullptr, + GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(DebugVertex), // stride + (void*)offsetof(DebugVertex, position) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the shader + 4, // 4 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(DebugVertex), // stride + (void*)offsetof(DebugVertex, rgba) // offset (0) + ); + glBindVertexArray(0); + // regardless of how many we use some fixed max + // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so + // indexing works properly. + m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); +} + +TFragment::~TFragment() { + discard_tree_cache(); + glDeleteVertexArrays(1, &m_debug_vao); } void TFragment::render(DmaFollower& dma, @@ -113,7 +149,7 @@ void TFragment::render(DmaFollower& dma, return; } { - m_tfrag3.setup_for_level(m_tree_kinds, level_name, render_state); + setup_for_level(m_tree_kinds, level_name, render_state); TfragRenderSettings settings; settings.hvdf_offset = m_tfrag_data.hvdf_offset; settings.fog = m_tfrag_data.fog; @@ -132,7 +168,7 @@ void TFragment::render(DmaFollower& dma, } auto t3prof = prof.make_scoped_child("t3"); - m_tfrag3.render_matching_trees(m_tfrag3.lod(), m_tree_kinds, settings, render_state, t3prof); + render_matching_trees(lod(), m_tree_kinds, settings, render_state, t3prof); } while (dma.current_tag_offset() != render_state->next_bucket) { @@ -142,11 +178,33 @@ void TFragment::render(DmaFollower& dma, } void TFragment::draw_debug_window() { - m_tfrag3.draw_debug_window(); + 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; + } + ImGui::PushID(i); + ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[lod()][i].kind]); + ImGui::SameLine(); + ImGui::Checkbox("Allow?", &tree.allowed); + ImGui::SameLine(); + ImGui::Checkbox("Force?", &tree.forced); + ImGui::SameLine(); + ImGui::Checkbox("cull debug (slow)", &tree.cull_debug); + ImGui::PopID(); + if (tree.rendered_this_frame) { + ImGui::Checkbox("freeze itimes", &tree.freeze_itimes); + ImGui::Text(" tris: %d draws: %d", tree.tris_this_frame, tree.draws_this_frame); + for (int j = 0; j < 4; j++) { + ImGui::Text(" itimes[%d] 0x%x 0x%x 0x%x 0x%x", j, tree.itimes_debug[j][0], + tree.itimes_debug[j][1], tree.itimes_debug[j][2], tree.itimes_debug[j][3]); + } + } + } } void TFragment::init_shaders(ShaderLibrary& shaders) { - m_tfrag3.init_shaders(shaders); + m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal"); } void TFragment::handle_initialization(DmaFollower& dma) { @@ -209,3 +267,457 @@ std::string TFragData::print() const { result += fmt::format("k1s[1]: {}\n", k1s[1].to_string_aligned()); return result; } + +void TFragment::update_load(const std::vector& tree_kinds, + const LevelData* loader_data) { + const auto* lev_data = loader_data->level.get(); + discard_tree_cache(); + for (int geom = 0; geom < GEOM_MAX; ++geom) { + m_cached_trees[geom].clear(); + } + + size_t time_of_day_count = 0; + size_t vis_temp_len = 0; + size_t max_draws = 0; + size_t max_num_grps = 0; + size_t max_inds = 0; + + for (int geom = 0; geom < GEOM_MAX; ++geom) { + for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) { + const auto& tree = lev_data->tfrag_trees[geom][tree_idx]; + + if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) { + auto& tree_cache = m_cached_trees[geom].emplace_back(); + tree_cache.kind = tree.kind; + max_draws = std::max(tree.draws.size(), max_draws); + size_t num_grps = 0; + for (auto& draw : tree.draws) { + num_grps += draw.vis_groups.size(); + } + max_num_grps = std::max(max_num_grps, num_grps); + max_inds = std::max(tree.unpacked.indices.size(), max_inds); + time_of_day_count = std::max(tree.colors.size(), time_of_day_count); + u32 verts = tree.packed_vertices.vertices.size(); + glGenVertexArrays(1, &tree_cache.vao); + glBindVertexArray(tree_cache.vao); + // glGenBuffers(1, &tree_cache.vertex_buffer); + tree_cache.vertex_buffer = loader_data->tfrag_vertex_data[geom][tree_idx]; + tree_cache.vert_count = verts; + tree_cache.draws = &tree.draws; // todo - should we just copy this? + tree_cache.colors = &tree.colors; + tree_cache.vis = &tree.bvh; + tree_cache.index_data = tree.unpacked.indices.data(); + tree_cache.tod_cache = swizzle_time_of_day(tree.colors); + tree_cache.draw_mode = tree.use_strips ? GL_TRIANGLE_STRIP : GL_TRIANGLES; + vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); + glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer); + // glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), + // nullptr, + // GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the shader + 3, // 3 values per vert + GL_FLOAT, // floats + GL_FALSE, // normalized + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) + ); + + glVertexAttribIPointer(2, // location 2 in the shader + 2, // 1 values per vert + GL_UNSIGNED_SHORT, // u16 + sizeof(tfrag3::PreloadedVertex), // stride + (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) + ); + glGenBuffers(1, &tree_cache.single_draw_index_buffer); + glGenBuffers(1, &tree_cache.index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, tree.unpacked.indices.size() * sizeof(u32), + tree.unpacked.indices.data(), GL_STREAM_DRAW); + + glGenTextures(1, &tree_cache.time_of_day_texture); + glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8, nullptr); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindVertexArray(0); + } + } + } + + m_cache.vis_temp.resize(vis_temp_len); + m_cache.multidraw_offset_per_stripdraw.resize(max_draws); + m_cache.multidraw_count_buffer.resize(max_num_grps); + m_cache.multidraw_index_offset_buffer.resize(max_num_grps); + m_cache.draw_idx_temp.resize(max_draws); + m_cache.index_temp.resize(max_inds); + ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); +} + +bool TFragment::setup_for_level(const std::vector& tree_kinds, + const std::string& level, + SharedRenderState* render_state) { + // make sure we have the level data. + Timer tfrag3_setup_timer; + auto lev_data = render_state->loader->get_tfrag3_level(level); + + if (!lev_data) { + // not loaded + m_has_level = false; + m_textures = nullptr; + m_level_name = ""; + discard_tree_cache(); + return false; + } + + if (m_has_level && lev_data->load_id != m_load_id) { + m_has_level = false; + m_textures = nullptr; + m_level_name = ""; + discard_tree_cache(); + return setup_for_level(tree_kinds, level, render_state); + } + + m_load_id = lev_data->load_id; + + if (m_level_name != level) { + update_load(tree_kinds, lev_data); + m_has_level = true; + m_textures = &lev_data->textures; + m_level_name = level; + } else { + m_has_level = true; + } + + if (tfrag3_setup_timer.getMs() > 5) { + lg::info("TFRAG setup: {:.1f}ms", tfrag3_setup_timer.getMs()); + } + + return m_has_level; +} + +void TFragment::render_tree(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + if (!m_has_level) { + return; + } + auto& tree = m_cached_trees.at(geom).at(settings.tree_idx); + const auto* itimes = settings.itimes; + + if (tree.freeze_itimes) { + itimes = tree.itimes_debug; + } else { + for (int i = 0; i < 4; i++) { + tree.itimes_debug[i] = settings.itimes[i]; + } + } + + ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID); + + if (m_color_result.size() < tree.colors->size()) { + m_color_result.resize(tree.colors->size()); + } +#ifndef __aarch64__ + if (m_use_fast_time_of_day) { + interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data()); + } else { + interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); + } +#else + interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); +#endif + glActiveTexture(GL_TEXTURE10); + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + m_color_result.data()); + + first_tfrag_draw_setup(settings, render_state, ShaderId::TFRAG3); + + glBindVertexArray(tree.vao); + glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, + render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer); + glActiveTexture(GL_TEXTURE0); + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(UINT32_MAX); + + cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, + m_cache.vis_temp.data()); + + u32 total_tris; + if (render_state->no_multidraw) { + u32 idx_buffer_size = make_index_list_from_vis_string( + m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp, + tree.index_data, &total_tris); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), m_cache.index_temp.data(), + GL_STREAM_DRAW); + } else { + total_tris = make_multidraws_from_vis_string( + m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(), + m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp); + } + + prof.add_tri(total_tris); + + for (size_t draw_idx = 0; draw_idx < tree.draws->size(); draw_idx++) { + const auto& draw = tree.draws->operator[](draw_idx); + const auto& multidraw_indices = m_cache.multidraw_offset_per_stripdraw[draw_idx]; + const auto& singledraw_indices = m_cache.draw_idx_temp[draw_idx]; + + if (render_state->no_multidraw) { + if (singledraw_indices.second == 0) { + continue; + } + } else { + if (multidraw_indices.second == 0) { + continue; + } + } + + ASSERT(m_textures); + s32 tex_idx = draw.tree_tex_id; + if (tex_idx >= 0) { + glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id)); + } else { + glBindTexture(GL_TEXTURE_2D, m_anim_slot_array->at(-(tex_idx + 1))); + } + auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3); + glUniform1i(m_uniforms.decal, draw.mode.get_decal() ? 1 : 0); + tree.tris_this_frame += draw.num_triangles; + tree.draws_this_frame++; + + prof.add_draw_call(); + if (render_state->no_multidraw) { + glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT, + (void*)(singledraw_indices.first * sizeof(u32))); + } else { + glMultiDrawElements(tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first], + GL_UNSIGNED_INT, + &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], + multidraw_indices.second); + } + + switch (double_draw.kind) { + case DoubleDrawKind::NONE: + break; + case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE: + prof.add_draw_call(); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"), + -10.f); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"), + double_draw.aref_second); + glDepthMask(GL_FALSE); + if (render_state->no_multidraw) { + glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT, + (void*)(singledraw_indices.first * sizeof(u32))); + } else { + glMultiDrawElements( + tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first], + GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], + multidraw_indices.second); + } + break; + default: + ASSERT(false); + } + } + glBindVertexArray(0); +} + +/*! + * Render all trees with settings for the given tree. + * This is intended to be used only for debugging when we can't easily get commands for all trees + * working. + */ +void TFragment::render_all_trees(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + TfragRenderSettings settings_copy = settings; + for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { + if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID) { + settings_copy.tree_idx = i; + render_tree(geom, settings_copy, render_state, prof); + } + } +} + +void TFragment::render_matching_trees(int geom, + const std::vector& trees, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + TfragRenderSettings settings_copy = settings; + for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { + auto& tree = m_cached_trees[geom][i]; + tree.reset_stats(); + if (!tree.allowed) { + continue; + } + if (std::find(trees.begin(), trees.end(), tree.kind) != trees.end() || tree.forced) { + tree.rendered_this_frame = true; + settings_copy.tree_idx = i; + render_tree(geom, settings_copy, render_state, prof); + if (tree.cull_debug) { + render_tree_cull_debug(settings_copy, render_state, prof); + } + } + } +} + +void TFragment::discard_tree_cache() { + m_textures = nullptr; + for (int geom = 0; geom < GEOM_MAX; ++geom) { + for (auto& tree : m_cached_trees[geom]) { + if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) { + glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); + glDeleteTextures(1, &tree.time_of_day_texture); + glDeleteBuffers(1, &tree.single_draw_index_buffer); + glDeleteBuffers(1, &tree.index_buffer); + glDeleteVertexArrays(1, &tree.vao); + } + } + m_cached_trees[geom].clear(); + } +} + +namespace { + +float frac(float in) { + return in - (int)in; +} + +void debug_vis_draw(int first_root, + int tree, + int num, + int depth, + const std::vector& nodes, + std::vector& verts_out) { + for (int ki = 0; ki < num; ki++) { + auto& node = nodes.at(ki + tree - first_root); + ASSERT(node.child_id != 0xffff); + math::Vector4f rgba{frac(0.4 * depth), frac(0.7 * depth), frac(0.2 * depth), 0.06}; + math::Vector3f center = node.bsphere.xyz(); + float rad = node.bsphere.w(); + math::Vector3f corners[8] = {center, center, center, center}; + corners[0].x() += rad; + corners[1].x() += rad; + corners[2].x() -= rad; + corners[3].x() -= rad; + + corners[0].y() += rad; + corners[1].y() -= rad; + corners[2].y() += rad; + corners[3].y() -= rad; + + for (int i = 0; i < 4; i++) { + corners[i + 4] = corners[i]; + corners[i].z() += rad; + corners[i + 4].z() -= rad; + } + + if (true) { + for (int i : {0, 4}) { + verts_out.push_back({corners[0 + i], rgba}); + verts_out.push_back({corners[1 + i], rgba}); + verts_out.push_back({corners[2 + i], rgba}); + + verts_out.push_back({corners[1 + i], rgba}); // 0 + verts_out.push_back({corners[3 + i], rgba}); + verts_out.push_back({corners[2 + i], rgba}); + } + + for (int i : {2, 6, 7, 2, 3, 7, 0, 4, 5, 0, 5, 1, 0, 6, 4, 0, 6, 2, 1, 3, 7, 1, 5, 7}) { + verts_out.push_back({corners[i], rgba}); + } + + constexpr int border0[12] = {0, 4, 6, 2, 2, 6, 3, 7, 0, 1, 2, 3}; + constexpr int border1[12] = {1, 5, 7, 3, 0, 4, 1, 5, 4, 5, 6, 7}; + rgba.w() = 1.0; + + for (int i = 0; i < 12; i++) { + auto p0 = corners[border0[i]]; + auto p1 = corners[border1[i]]; + auto diff = (p1 - p0).normalized(); + math::Vector3f px = diff.z() == 0 ? math::Vector3f{1, 0, 1} : math::Vector3f{0, 1, 1}; + auto off = diff.cross(px) * 2000; + + verts_out.push_back({p0 + off, rgba}); + verts_out.push_back({p0 - off, rgba}); + verts_out.push_back({p1 - off, rgba}); + + verts_out.push_back({p0 + off, rgba}); + verts_out.push_back({p1 + off, rgba}); + verts_out.push_back({p1 - off, rgba}); + } + } + + if (node.flags) { + debug_vis_draw(first_root, node.child_id, node.num_kids, depth + 1, nodes, verts_out); + } + } +} + +} // namespace + +void TFragment::render_tree_cull_debug(const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof) { + // generate debug verts: + m_debug_vert_data.clear(); + auto& tree = m_cached_trees.at(settings.tree_idx).at(lod()); + + debug_vis_draw(tree.vis->first_root, tree.vis->first_root, tree.vis->num_roots, 1, + tree.vis->vis_nodes, m_debug_vert_data); + + render_state->shaders[ShaderId::TFRAG3_NO_TEX].activate(); + glUniformMatrix4fv( + glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "camera"), 1, + GL_FALSE, settings.math_camera.data()); + glUniform4f( + glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"), + settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2], + settings.hvdf_offset[3]); + glUniform1f( + glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"), + settings.fog.x()); + // glDisable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_GEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ? + glDepthMask(GL_FALSE); + + glBindVertexArray(m_debug_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts); + + int remaining = m_debug_vert_data.size(); + int start = 0; + + while (remaining > 0) { + int to_do = std::min(DEBUG_TRI_COUNT * 3, remaining); + + glBufferSubData(GL_ARRAY_BUFFER, 0, to_do * sizeof(DebugVertex), + m_debug_vert_data.data() + start); + glDrawArrays(GL_TRIANGLES, 0, to_do); + prof.add_draw_call(); + prof.add_tri(to_do / 3); + + remaining -= to_do; + start += to_do; + } +} diff --git a/game/graphics/opengl_renderer/background/TFragment.h b/game/graphics/opengl_renderer/background/TFragment.h index cb7d61b38f..39890e7f3c 100644 --- a/game/graphics/opengl_renderer/background/TFragment.h +++ b/game/graphics/opengl_renderer/background/TFragment.h @@ -5,7 +5,6 @@ #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/DirectRenderer.h" -#include "game/graphics/opengl_renderer/background/Tfrag3.h" #include "game/graphics/opengl_renderer/background/Tie3.h" using math::Matrix4f; @@ -40,14 +39,49 @@ class TFragment : public BucketRenderer { int my_id, const std::vector& trees, bool child_mode, - int level_id); + int level_id, + const std::vector* anim_slot_array); + ~TFragment(); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; void init_shaders(ShaderLibrary& shaders) override; + void render_all_trees(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof); + + void render_matching_trees(int geom, + const std::vector& trees, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof); + + void render_tree(int geom, + const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof); + + bool setup_for_level(const std::vector& tree_kinds, + const std::string& level, + SharedRenderState* render_state); + void discard_tree_cache(); + + void render_tree_cull_debug(const TfragRenderSettings& settings, + SharedRenderState* render_state, + ScopedProfilerNode& prof); + + void update_load(const std::vector& tree_kinds, + const LevelData* loader_data); + + int lod() const { return Gfx::g_global_settings.lod_tfrag; } + struct DebugVertex { + math::Vector3f position; + math::Vector4f rgba; + }; + private: void handle_initialization(DmaFollower& dma); - bool m_child_mode = false; // GS setup data @@ -76,7 +110,75 @@ class TFragment : public BucketRenderer { TFragSetup = 0, }; - Tfrag3 m_tfrag3; std::vector m_tree_kinds; int m_level_id; + + static constexpr int GEOM_MAX = 3; + + struct TreeCache { + tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID; + GLuint vertex_buffer = -1; + GLuint index_buffer = -1; + GLuint single_draw_index_buffer = -1; + GLuint time_of_day_texture = -1; + GLuint vao = -1; + u32 vert_count = 0; + const std::vector* draws = nullptr; + const std::vector* colors = nullptr; + const tfrag3::BVH* vis = nullptr; + const u32* index_data = nullptr; + SwizzledTimeOfDay tod_cache; + u64 draw_mode = 0; + + void reset_stats() { + rendered_this_frame = false; + tris_this_frame = 0; + draws_this_frame = 0; + } + bool rendered_this_frame = false; + int tris_this_frame = 0; + int draws_this_frame = 0; + bool allowed = true; + bool forced = false; + bool cull_debug = false; + + bool freeze_itimes = false; + math::Vector itimes_debug[4]; + }; + + struct { + GLuint decal; + } m_uniforms; + + struct Cache { + std::vector vis_temp; + std::vector> draw_idx_temp; + std::vector index_temp; + std::vector> multidraw_offset_per_stripdraw; + std::vector multidraw_count_buffer; + std::vector multidraw_index_offset_buffer; + } m_cache; + + std::string m_level_name; + + const std::vector* m_textures = nullptr; + std::array, GEOM_MAX> m_cached_trees; + + std::vector> m_color_result; + + GLuint m_debug_vao = -1; + GLuint m_debug_verts = -1; + + u64 m_load_id = -1; + + // in theory could be up to 4096, I think, but we don't see that many... + // should be easy to increase + static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192; + + static constexpr int DEBUG_TRI_COUNT = 4096; + std::vector m_debug_vert_data; + + bool m_has_level = false; + bool m_use_fast_time_of_day = true; + const std::vector* m_anim_slot_array; }; diff --git a/game/graphics/opengl_renderer/background/Tfrag3.cpp b/game/graphics/opengl_renderer/background/Tfrag3.cpp deleted file mode 100644 index 71fd7087d5..0000000000 --- a/game/graphics/opengl_renderer/background/Tfrag3.cpp +++ /dev/null @@ -1,520 +0,0 @@ -#include "Tfrag3.h" - -#include "common/log/log.h" - -#include "third-party/imgui/imgui.h" - -Tfrag3::Tfrag3() { - glGenVertexArrays(1, &m_debug_vao); - glBindVertexArray(m_debug_vao); - glGenBuffers(1, &m_debug_verts); - glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts); - glBufferData(GL_ARRAY_BUFFER, DEBUG_TRI_COUNT * 3 * sizeof(DebugVertex), nullptr, - GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(0, // location 0 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(DebugVertex), // stride - (void*)offsetof(DebugVertex, position) // offset (0) - ); - - glVertexAttribPointer(1, // location 1 in the shader - 4, // 4 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(DebugVertex), // stride - (void*)offsetof(DebugVertex, rgba) // offset (0) - ); - glBindVertexArray(0); - // regardless of how many we use some fixed max - // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so - // indexing works properly. - m_color_result.resize(TIME_OF_DAY_COLOR_COUNT); -} - -Tfrag3::~Tfrag3() { - discard_tree_cache(); - glDeleteVertexArrays(1, &m_debug_vao); -} - -void Tfrag3::init_shaders(ShaderLibrary& shaders) { - m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::TFRAG3].id(), "decal"); -} - -void Tfrag3::update_load(const std::vector& tree_kinds, - const LevelData* loader_data) { - const auto* lev_data = loader_data->level.get(); - discard_tree_cache(); - for (int geom = 0; geom < GEOM_MAX; ++geom) { - m_cached_trees[geom].clear(); - } - - size_t time_of_day_count = 0; - size_t vis_temp_len = 0; - size_t max_draws = 0; - size_t max_num_grps = 0; - size_t max_inds = 0; - - for (int geom = 0; geom < GEOM_MAX; ++geom) { - for (size_t tree_idx = 0; tree_idx < lev_data->tfrag_trees[geom].size(); tree_idx++) { - const auto& tree = lev_data->tfrag_trees[geom][tree_idx]; - - if (std::find(tree_kinds.begin(), tree_kinds.end(), tree.kind) != tree_kinds.end()) { - auto& tree_cache = m_cached_trees[geom].emplace_back(); - tree_cache.kind = tree.kind; - max_draws = std::max(tree.draws.size(), max_draws); - size_t num_grps = 0; - for (auto& draw : tree.draws) { - num_grps += draw.vis_groups.size(); - } - max_num_grps = std::max(max_num_grps, num_grps); - max_inds = std::max(tree.unpacked.indices.size(), max_inds); - time_of_day_count = std::max(tree.colors.size(), time_of_day_count); - u32 verts = tree.packed_vertices.vertices.size(); - glGenVertexArrays(1, &tree_cache.vao); - glBindVertexArray(tree_cache.vao); - // glGenBuffers(1, &tree_cache.vertex_buffer); - tree_cache.vertex_buffer = loader_data->tfrag_vertex_data[geom][tree_idx]; - tree_cache.vert_count = verts; - tree_cache.draws = &tree.draws; // todo - should we just copy this? - tree_cache.colors = &tree.colors; - tree_cache.vis = &tree.bvh; - tree_cache.index_data = tree.unpacked.indices.data(); - tree_cache.tod_cache = swizzle_time_of_day(tree.colors); - tree_cache.draw_mode = tree.use_strips ? GL_TRIANGLE_STRIP : GL_TRIANGLES; - vis_temp_len = std::max(vis_temp_len, tree.bvh.vis_nodes.size()); - glBindBuffer(GL_ARRAY_BUFFER, tree_cache.vertex_buffer); - // glBufferData(GL_ARRAY_BUFFER, verts * sizeof(tfrag3::PreloadedVertex), - // nullptr, - // GL_STREAM_DRAW); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, // location 0 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, x) // offset (0) - ); - - glVertexAttribPointer(1, // location 1 in the shader - 3, // 3 values per vert - GL_FLOAT, // floats - GL_FALSE, // normalized - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, s) // offset (0) - ); - - glVertexAttribIPointer(2, // location 2 in the shader - 2, // 1 values per vert - GL_UNSIGNED_SHORT, // u16 - sizeof(tfrag3::PreloadedVertex), // stride - (void*)offsetof(tfrag3::PreloadedVertex, color_index) // offset (0) - ); - glGenBuffers(1, &tree_cache.single_draw_index_buffer); - glGenBuffers(1, &tree_cache.index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tree_cache.index_buffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, tree.unpacked.indices.size() * sizeof(u32), - tree.unpacked.indices.data(), GL_STREAM_DRAW); - - glGenTextures(1, &tree_cache.time_of_day_texture); - glBindTexture(GL_TEXTURE_1D, tree_cache.time_of_day_texture); - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, TIME_OF_DAY_COLOR_COUNT, 0, GL_RGBA, - GL_UNSIGNED_INT_8_8_8_8, nullptr); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindVertexArray(0); - } - } - } - - m_cache.vis_temp.resize(vis_temp_len); - m_cache.multidraw_offset_per_stripdraw.resize(max_draws); - m_cache.multidraw_count_buffer.resize(max_num_grps); - m_cache.multidraw_index_offset_buffer.resize(max_num_grps); - m_cache.draw_idx_temp.resize(max_draws); - m_cache.index_temp.resize(max_inds); - ASSERT(time_of_day_count <= TIME_OF_DAY_COLOR_COUNT); -} - -bool Tfrag3::setup_for_level(const std::vector& tree_kinds, - const std::string& level, - SharedRenderState* render_state) { - // make sure we have the level data. - Timer tfrag3_setup_timer; - auto lev_data = render_state->loader->get_tfrag3_level(level); - - if (!lev_data) { - // not loaded - m_has_level = false; - m_textures = nullptr; - m_level_name = ""; - discard_tree_cache(); - return false; - } - - if (m_has_level && lev_data->load_id != m_load_id) { - m_has_level = false; - m_textures = nullptr; - m_level_name = ""; - discard_tree_cache(); - return setup_for_level(tree_kinds, level, render_state); - } - - m_load_id = lev_data->load_id; - - if (m_level_name != level) { - update_load(tree_kinds, lev_data); - m_has_level = true; - m_textures = &lev_data->textures; - m_level_name = level; - } else { - m_has_level = true; - } - - if (tfrag3_setup_timer.getMs() > 5) { - lg::info("TFRAG setup: {:.1f}ms", tfrag3_setup_timer.getMs()); - } - - return m_has_level; -} - -void Tfrag3::render_tree(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { - if (!m_has_level) { - return; - } - auto& tree = m_cached_trees.at(geom).at(settings.tree_idx); - const auto* itimes = settings.itimes; - - if (tree.freeze_itimes) { - itimes = tree.itimes_debug; - } else { - for (int i = 0; i < 4; i++) { - tree.itimes_debug[i] = settings.itimes[i]; - } - } - - ASSERT(tree.kind != tfrag3::TFragmentTreeKind::INVALID); - - if (m_color_result.size() < tree.colors->size()) { - m_color_result.resize(tree.colors->size()); - } -#ifndef __aarch64__ - if (m_use_fast_time_of_day) { - interp_time_of_day_fast(settings.itimes, tree.tod_cache, m_color_result.data()); - } else { - interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); - } -#else - interp_time_of_day_slow(settings.itimes, *tree.colors, m_color_result.data()); -#endif - glActiveTexture(GL_TEXTURE10); - glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, tree.colors->size(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, - m_color_result.data()); - - first_tfrag_draw_setup(settings, render_state, ShaderId::TFRAG3); - - glBindVertexArray(tree.vao); - glBindBuffer(GL_ARRAY_BUFFER, tree.vertex_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, - render_state->no_multidraw ? tree.single_draw_index_buffer : tree.index_buffer); - glActiveTexture(GL_TEXTURE0); - glEnable(GL_PRIMITIVE_RESTART); - glPrimitiveRestartIndex(UINT32_MAX); - - cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, - m_cache.vis_temp.data()); - - u32 total_tris; - if (render_state->no_multidraw) { - u32 idx_buffer_size = make_index_list_from_vis_string( - m_cache.draw_idx_temp.data(), m_cache.index_temp.data(), *tree.draws, m_cache.vis_temp, - tree.index_data, &total_tris); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size * sizeof(u32), m_cache.index_temp.data(), - GL_STREAM_DRAW); - } else { - total_tris = make_multidraws_from_vis_string( - m_cache.multidraw_offset_per_stripdraw.data(), m_cache.multidraw_count_buffer.data(), - m_cache.multidraw_index_offset_buffer.data(), *tree.draws, m_cache.vis_temp); - } - - prof.add_tri(total_tris); - - for (size_t draw_idx = 0; draw_idx < tree.draws->size(); draw_idx++) { - const auto& draw = tree.draws->operator[](draw_idx); - const auto& multidraw_indices = m_cache.multidraw_offset_per_stripdraw[draw_idx]; - const auto& singledraw_indices = m_cache.draw_idx_temp[draw_idx]; - - if (render_state->no_multidraw) { - if (singledraw_indices.second == 0) { - continue; - } - } else { - if (multidraw_indices.second == 0) { - continue; - } - } - - ASSERT(m_textures); - glBindTexture(GL_TEXTURE_2D, m_textures->at(draw.tree_tex_id)); - auto double_draw = setup_tfrag_shader(render_state, draw.mode, ShaderId::TFRAG3); - glUniform1i(m_uniforms.decal, draw.mode.get_decal() ? 1 : 0); - tree.tris_this_frame += draw.num_triangles; - tree.draws_this_frame++; - - prof.add_draw_call(); - if (render_state->no_multidraw) { - glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT, - (void*)(singledraw_indices.first * sizeof(u32))); - } else { - glMultiDrawElements(tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first], - GL_UNSIGNED_INT, - &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], - multidraw_indices.second); - } - - switch (double_draw.kind) { - case DoubleDrawKind::NONE: - break; - case DoubleDrawKind::AFAIL_NO_DEPTH_WRITE: - prof.add_draw_call(); - glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_min"), - -10.f); - glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3].id(), "alpha_max"), - double_draw.aref_second); - glDepthMask(GL_FALSE); - if (render_state->no_multidraw) { - glDrawElements(tree.draw_mode, singledraw_indices.second, GL_UNSIGNED_INT, - (void*)(singledraw_indices.first * sizeof(u32))); - } else { - glMultiDrawElements( - tree.draw_mode, &m_cache.multidraw_count_buffer[multidraw_indices.first], - GL_UNSIGNED_INT, &m_cache.multidraw_index_offset_buffer[multidraw_indices.first], - multidraw_indices.second); - } - break; - default: - ASSERT(false); - } - } - glBindVertexArray(0); -} - -/*! - * Render all trees with settings for the given tree. - * This is intended to be used only for debugging when we can't easily get commands for all trees - * working. - */ -void Tfrag3::render_all_trees(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { - TfragRenderSettings settings_copy = settings; - for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { - if (m_cached_trees[geom][i].kind != tfrag3::TFragmentTreeKind::INVALID) { - settings_copy.tree_idx = i; - render_tree(geom, settings_copy, render_state, prof); - } - } -} - -void Tfrag3::render_matching_trees(int geom, - const std::vector& trees, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { - TfragRenderSettings settings_copy = settings; - for (size_t i = 0; i < m_cached_trees[geom].size(); i++) { - auto& tree = m_cached_trees[geom][i]; - tree.reset_stats(); - if (!tree.allowed) { - continue; - } - if (std::find(trees.begin(), trees.end(), tree.kind) != trees.end() || tree.forced) { - tree.rendered_this_frame = true; - settings_copy.tree_idx = i; - render_tree(geom, settings_copy, render_state, prof); - if (tree.cull_debug) { - render_tree_cull_debug(settings_copy, render_state, prof); - } - } - } -} - -void Tfrag3::draw_debug_window() { - 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; - } - ImGui::PushID(i); - ImGui::Text("[%d] %10s", i, tfrag3::tfrag_tree_names[(int)m_cached_trees[lod()][i].kind]); - ImGui::SameLine(); - ImGui::Checkbox("Allow?", &tree.allowed); - ImGui::SameLine(); - ImGui::Checkbox("Force?", &tree.forced); - ImGui::SameLine(); - ImGui::Checkbox("cull debug (slow)", &tree.cull_debug); - ImGui::PopID(); - if (tree.rendered_this_frame) { - ImGui::Checkbox("freeze itimes", &tree.freeze_itimes); - ImGui::Text(" tris: %d draws: %d", tree.tris_this_frame, tree.draws_this_frame); - for (int j = 0; j < 4; j++) { - ImGui::Text(" itimes[%d] 0x%x 0x%x 0x%x 0x%x", j, tree.itimes_debug[j][0], - tree.itimes_debug[j][1], tree.itimes_debug[j][2], tree.itimes_debug[j][3]); - } - } - } -} - -void Tfrag3::discard_tree_cache() { - m_textures = nullptr; - for (int geom = 0; geom < GEOM_MAX; ++geom) { - for (auto& tree : m_cached_trees[geom]) { - if (tree.kind != tfrag3::TFragmentTreeKind::INVALID) { - glBindTexture(GL_TEXTURE_1D, tree.time_of_day_texture); - glDeleteTextures(1, &tree.time_of_day_texture); - glDeleteBuffers(1, &tree.single_draw_index_buffer); - glDeleteBuffers(1, &tree.index_buffer); - glDeleteVertexArrays(1, &tree.vao); - } - } - m_cached_trees[geom].clear(); - } -} - -namespace { - -float frac(float in) { - return in - (int)in; -} - -void debug_vis_draw(int first_root, - int tree, - int num, - int depth, - const std::vector& nodes, - std::vector& verts_out) { - for (int ki = 0; ki < num; ki++) { - auto& node = nodes.at(ki + tree - first_root); - ASSERT(node.child_id != 0xffff); - math::Vector4f rgba{frac(0.4 * depth), frac(0.7 * depth), frac(0.2 * depth), 0.06}; - math::Vector3f center = node.bsphere.xyz(); - float rad = node.bsphere.w(); - math::Vector3f corners[8] = {center, center, center, center}; - corners[0].x() += rad; - corners[1].x() += rad; - corners[2].x() -= rad; - corners[3].x() -= rad; - - corners[0].y() += rad; - corners[1].y() -= rad; - corners[2].y() += rad; - corners[3].y() -= rad; - - for (int i = 0; i < 4; i++) { - corners[i + 4] = corners[i]; - corners[i].z() += rad; - corners[i + 4].z() -= rad; - } - - if (true) { - for (int i : {0, 4}) { - verts_out.push_back({corners[0 + i], rgba}); - verts_out.push_back({corners[1 + i], rgba}); - verts_out.push_back({corners[2 + i], rgba}); - - verts_out.push_back({corners[1 + i], rgba}); // 0 - verts_out.push_back({corners[3 + i], rgba}); - verts_out.push_back({corners[2 + i], rgba}); - } - - for (int i : {2, 6, 7, 2, 3, 7, 0, 4, 5, 0, 5, 1, 0, 6, 4, 0, 6, 2, 1, 3, 7, 1, 5, 7}) { - verts_out.push_back({corners[i], rgba}); - } - - constexpr int border0[12] = {0, 4, 6, 2, 2, 6, 3, 7, 0, 1, 2, 3}; - constexpr int border1[12] = {1, 5, 7, 3, 0, 4, 1, 5, 4, 5, 6, 7}; - rgba.w() = 1.0; - - for (int i = 0; i < 12; i++) { - auto p0 = corners[border0[i]]; - auto p1 = corners[border1[i]]; - auto diff = (p1 - p0).normalized(); - math::Vector3f px = diff.z() == 0 ? math::Vector3f{1, 0, 1} : math::Vector3f{0, 1, 1}; - auto off = diff.cross(px) * 2000; - - verts_out.push_back({p0 + off, rgba}); - verts_out.push_back({p0 - off, rgba}); - verts_out.push_back({p1 - off, rgba}); - - verts_out.push_back({p0 + off, rgba}); - verts_out.push_back({p1 + off, rgba}); - verts_out.push_back({p1 - off, rgba}); - } - } - - if (node.flags) { - debug_vis_draw(first_root, node.child_id, node.num_kids, depth + 1, nodes, verts_out); - } - } -} - -} // namespace - -void Tfrag3::render_tree_cull_debug(const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof) { - // generate debug verts: - m_debug_vert_data.clear(); - auto& tree = m_cached_trees.at(settings.tree_idx).at(lod()); - - debug_vis_draw(tree.vis->first_root, tree.vis->first_root, tree.vis->num_roots, 1, - tree.vis->vis_nodes, m_debug_vert_data); - - render_state->shaders[ShaderId::TFRAG3_NO_TEX].activate(); - glUniformMatrix4fv( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "camera"), 1, - GL_FALSE, settings.math_camera.data()); - glUniform4f( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "hvdf_offset"), - settings.hvdf_offset[0], settings.hvdf_offset[1], settings.hvdf_offset[2], - settings.hvdf_offset[3]); - glUniform1f( - glGetUniformLocation(render_state->shaders[ShaderId::TFRAG3_NO_TEX].id(), "fog_constant"), - settings.fog.x()); - // glDisable(GL_DEPTH_TEST); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GEQUAL); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // ? - glDepthMask(GL_FALSE); - - glBindVertexArray(m_debug_vao); - glBindBuffer(GL_ARRAY_BUFFER, m_debug_verts); - - int remaining = m_debug_vert_data.size(); - int start = 0; - - while (remaining > 0) { - int to_do = std::min(DEBUG_TRI_COUNT * 3, remaining); - - glBufferSubData(GL_ARRAY_BUFFER, 0, to_do * sizeof(DebugVertex), - m_debug_vert_data.data() + start); - glDrawArrays(GL_TRIANGLES, 0, to_do); - prof.add_draw_call(); - prof.add_tri(to_do / 3); - - remaining -= to_do; - start += to_do; - } -} diff --git a/game/graphics/opengl_renderer/background/Tfrag3.h b/game/graphics/opengl_renderer/background/Tfrag3.h deleted file mode 100644 index 4ebbdaf355..0000000000 --- a/game/graphics/opengl_renderer/background/Tfrag3.h +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -#include "common/custom_data/Tfrag3Data.h" -#include "common/math/Vector.h" - -#include "game/graphics/gfx.h" -#include "game/graphics/opengl_renderer/BucketRenderer.h" -#include "game/graphics/opengl_renderer/background/background_common.h" -#include "game/graphics/pipelines/opengl.h" - -class Tfrag3 { - public: - Tfrag3(); - ~Tfrag3(); - - void init_shaders(ShaderLibrary& shaders); - void render_all_trees(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - - void render_matching_trees(int geom, - const std::vector& trees, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - - void render_tree(int geom, - const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - - bool setup_for_level(const std::vector& tree_kinds, - const std::string& level, - SharedRenderState* render_state); - void discard_tree_cache(); - - void render_tree_cull_debug(const TfragRenderSettings& settings, - SharedRenderState* render_state, - ScopedProfilerNode& prof); - - void draw_debug_window(); - struct DebugVertex { - math::Vector3f position; - math::Vector4f rgba; - }; - - void update_load(const std::vector& tree_kinds, - const LevelData* loader_data); - - int lod() const { return Gfx::g_global_settings.lod_tfrag; } - - private: - static constexpr int GEOM_MAX = 3; - - struct TreeCache { - tfrag3::TFragmentTreeKind kind = tfrag3::TFragmentTreeKind::INVALID; - GLuint vertex_buffer = -1; - GLuint index_buffer = -1; - GLuint single_draw_index_buffer = -1; - GLuint time_of_day_texture = -1; - GLuint vao = -1; - u32 vert_count = 0; - const std::vector* draws = nullptr; - const std::vector* colors = nullptr; - const tfrag3::BVH* vis = nullptr; - const u32* index_data = nullptr; - SwizzledTimeOfDay tod_cache; - u64 draw_mode = 0; - - void reset_stats() { - rendered_this_frame = false; - tris_this_frame = 0; - draws_this_frame = 0; - } - bool rendered_this_frame = false; - int tris_this_frame = 0; - int draws_this_frame = 0; - bool allowed = true; - bool forced = false; - bool cull_debug = false; - - bool freeze_itimes = false; - math::Vector itimes_debug[4]; - }; - - struct { - GLuint decal; - } m_uniforms; - - struct Cache { - std::vector vis_temp; - std::vector> draw_idx_temp; - std::vector index_temp; - std::vector> multidraw_offset_per_stripdraw; - std::vector multidraw_count_buffer; - std::vector multidraw_index_offset_buffer; - } m_cache; - - std::string m_level_name; - - const std::vector* m_textures = nullptr; - std::array, GEOM_MAX> m_cached_trees; - - std::vector> m_color_result; - - GLuint m_debug_vao = -1; - GLuint m_debug_verts = -1; - - u64 m_load_id = -1; - - // in theory could be up to 4096, I think, but we don't see that many... - // should be easy to increase - static constexpr int TIME_OF_DAY_COLOR_COUNT = 8192; - - static constexpr int DEBUG_TRI_COUNT = 4096; - std::vector m_debug_vert_data; - - bool m_has_level = false; - bool m_use_fast_time_of_day = true; -}; diff --git a/game/graphics/opengl_renderer/shaders/tex_anim.frag b/game/graphics/opengl_renderer/shaders/tex_anim.frag index ce98b70ed3..5762ce3faa 100644 --- a/game/graphics/opengl_renderer/shaders/tex_anim.frag +++ b/game/graphics/opengl_renderer/shaders/tex_anim.frag @@ -6,6 +6,7 @@ uniform vec4 rgba; uniform int enable_tex; uniform int tcc; uniform ivec4 channel_scramble; +uniform float alpha_multiply; in vec2 uv; @@ -28,4 +29,6 @@ void main() { } else { color = (rgba / 128.); } + + color.a *= alpha_multiply; } \ No newline at end of file diff --git a/goal_src/jak2/engine/gfx/texture/texture-anim.gc b/goal_src/jak2/engine/gfx/texture/texture-anim.gc index 61390715d3..cdc5c112f8 100644 --- a/goal_src/jak2/engine/gfx/texture/texture-anim.gc +++ b/goal_src/jak2/engine/gfx/texture/texture-anim.gc @@ -182,6 +182,9 @@ (oracle-jak 24) (nest-jak 25) (kor-transform 26) + (skull-gem 27) + (bomb 28) + (cas-conveyor 29) ) (deftype texture-anim-pc-upload (structure) @@ -212,6 +215,15 @@ ) ) +(defmacro pc-texture-anim-flag-id (kind buf &key (qwc 0)) + `(dma-buffer-add-cnt-vif2 + ,buf + ,qwc + (new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm ,kind) + (new 'static 'vif-tag) + ) + ) + (defun texture-anim-layer-add-shader ((arg0 dma-buffer) (arg1 texture-anim-layer) (arg2 int)) "Add DMA to set the GS to use the given shader. This can be used to read from the texture." (let ((s5-0 (-> arg1 tex))) @@ -576,6 +588,58 @@ (define-extern *darkjak-hires-texture-anim-array* (texture-anim-array texture-anim)) (define-extern *darkjak-hires-nest-texture-anim-array* (texture-anim-array texture-anim)) (define-extern *kor-transform-texture-anim-array* (texture-anim-array texture-anim)) +(define-extern *skull-gem-texture-anim-array* (texture-anim-array texture-anim)) +(define-extern *bomb-texture-anim-array* (texture-anim-array texture-anim)) +(define-extern *cas-conveyor-texture-anim-array* (texture-anim-array texture-anim)) + +(defun pc-update-fixed-anim ((bucket bucket-id) (anim-id texture-anim-pc) (anim-array texture-anim-array)) + "Run a 'fixed' texture-anim, which should run entirely in C++." + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + ;; determine how many texture we have: + (let ((num 0)) + (dotimes (i (-> anim-array length)) + (when (-> anim-array array-data i tex) + (+! num 1) + ) + ) + + ;; (format 0 "pc-update-fixed-anim: ~d layers~%" num) + (let ((num-qw (/ (+ num 3) 4))) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag-id anim-id dma-buf :qwc num-qw) + + (dotimes (i num) + (let ((anim (-> anim-array array-data i)) + (out-slot (the (pointer float) (&+ (-> dma-buf base) (* 4 i)))) + ) + + (set! (-> out-slot) (-> anim frame-time)) + + (when (not (paused?)) + (with-pp + (let ((f0-2 (+ (-> anim frame-time) (* (-> anim frame-delta) (-> pp clock seconds-per-frame)))) + (f1-2 (-> anim frame-mod)) + ) + (set! (-> anim frame-time) (- f0-2 (* (the float (the int (/ f0-2 f1-2))) f1-2))) + ) + (if (< (-> anim frame-time) 0.0) + (+! (-> anim frame-time) (-> anim frame-mod)) + ) + ) + ) + ) + ) + + (&+! (-> dma-buf base) (* 16 num-qw)) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + ) + ) + + (none) + ) (defun update-texture-anim ((bucket bucket-id) (anim-array texture-anim-array)) @@ -593,6 +657,18 @@ ;; for sky, we basically emulate the full thing ;; (format *stdcon* "doing sky to bucket ~d~%" bucket) ) + ((= anim-array *skull-gem-texture-anim-array*) + (pc-update-fixed-anim bucket (texture-anim-pc skull-gem) anim-array) + (return #f) + ) + ((= anim-array *bomb-texture-anim-array*) + (pc-update-fixed-anim bucket (texture-anim-pc bomb) anim-array) + (return #f) + ) + ((= anim-array *cas-conveyor-texture-anim-array*) + (pc-update-fixed-anim bucket (texture-anim-pc cas-conveyor) anim-array) + (return #f) + ) ((= anim-array *darkjak-texture-anim-array*) ;; darkjak is simple, and we reimplemented it in C++. ;; so we just have to send the frame-time value.