diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 3bdfe1f98..abb7612c2 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -56,6 +56,7 @@ add_library(common serialization/subtitles/subtitles.cpp serialization/text/text_ser.cpp sqlite/sqlite.cpp + texture/texture_slots.cpp type_system/defenum.cpp type_system/deftype.cpp type_system/state.cpp diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp index 9b1e9b147..469c68ed0 100644 --- a/common/custom_data/TFrag3Data.cpp +++ b/common/custom_data/TFrag3Data.cpp @@ -433,6 +433,17 @@ void Texture::serialize(Serializer& ser) { ser.from_ptr(&load_to_pool); } +void IndexTexture::serialize(Serializer& ser) { + ser.from_ptr(&w); + ser.from_ptr(&h); + ser.from_ptr(&combo_id); + ser.from_pod_vector(&index_data); + ser.from_ptr(&color_table); + ser.from_str(&name); + ser.from_str(&tpage_name); + ser.from_string_vector(&level_names); +} + void CollisionMesh::serialize(Serializer& ser) { ser.from_pod_vector(&vertices); } @@ -545,6 +556,15 @@ void Level::serialize(Serializer& ser) { tex.serialize(ser); } + if (ser.is_saving()) { + ser.save(index_textures.size()); + } else { + index_textures.resize(ser.load()); + } + for (auto& tex : index_textures) { + tex.serialize(ser); + } + for (int geom = 0; geom < 3; ++geom) { if (ser.is_saving()) { ser.save(tfrag_trees[geom].size()); @@ -687,6 +707,11 @@ void Texture::memory_usage(MemoryUsageTracker* tracker) const { tracker->add(MemoryUsageCategory::TEXTURE, data.size() * sizeof(u32)); } +void IndexTexture::memory_usage(MemoryUsageTracker* tracker) const { + tracker->add(MemoryUsageCategory::SPECIAL_TEXTURE, index_data.size()); + tracker->add(MemoryUsageCategory::SPECIAL_TEXTURE, 256 * 4); // clut +} + void Level::memory_usage(MemoryUsageTracker* tracker) const { for (const auto& texture : textures) { texture.memory_usage(tracker); @@ -715,6 +740,7 @@ void print_memory_usage(const tfrag3::Level& lev, int uncompressed_data_size) { std::vector> known_categories = { {"texture", mem_use.data[tfrag3::MemoryUsageCategory::TEXTURE]}, + {"special-texture", mem_use.data[tfrag3::MemoryUsageCategory::SPECIAL_TEXTURE]}, {"tie-deinst-vis", mem_use.data[tfrag3::MemoryUsageCategory::TIE_DEINST_VIS]}, {"tie-deinst-idx", mem_use.data[tfrag3::MemoryUsageCategory::TIE_DEINST_INDEX]}, {"tie-inst-vis", mem_use.data[tfrag3::MemoryUsageCategory::TIE_INST_VIS]}, diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 6a3080271..680d36aaa 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -18,11 +18,13 @@ namespace tfrag3 { // - if changing any large things (vertices, vis, bvh, colors, textures) update get_memory_usage // - if adding a new category to the memory usage, update extract_level to print it. -constexpr int TFRAG3_VERSION = 37; +constexpr int TFRAG3_VERSION = 38; enum MemoryUsageCategory { TEXTURE, + SPECIAL_TEXTURE, + TIE_DEINST_VIS, TIE_DEINST_INDEX, TIE_INST_VIS, @@ -292,6 +294,18 @@ struct Texture { void memory_usage(MemoryUsageTracker* tracker) const; }; +struct IndexTexture { + u16 w, h; + u32 combo_id = 0; + std::vector index_data; + std::vector level_names; + std::string name; + std::string tpage_name; + std::array, 256> color_table; + void serialize(Serializer& ser); + void memory_usage(MemoryUsageTracker* tracker) const; +}; + // Tfrag trees have several kinds: enum class TFragmentTreeKind { NORMAL, TRANS, DIRT, ICE, LOWRES, LOWRES_TRANS, WATER, INVALID }; @@ -458,7 +472,7 @@ static_assert(sizeof(MercVertex) == 64); struct MercDraw { DrawMode mode; - 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) u8 eye_id = 0xff; // 0xff if not eyes, (slot << 1) | (is_r) u32 first_index; u32 index_count; @@ -542,6 +556,7 @@ struct Level { u16 version = TFRAG3_VERSION; std::string level_name; std::vector textures; + std::vector index_textures; std::array, TFRAG_GEOS> tfrag_trees; std::array, TIE_GEOS> tie_trees; std::vector shrub_trees; diff --git a/common/dma/gs.cpp b/common/dma/gs.cpp index e886748c0..5fd847760 100644 --- a/common/dma/gs.cpp +++ b/common/dma/gs.cpp @@ -276,6 +276,7 @@ std::string GsTest::print() const { ASSERT(false); } } + result += '\n'; return result; } diff --git a/common/serialization/subtitles/subtitles.cpp b/common/serialization/subtitles/subtitles.cpp index 2c31402ab..5c80fb2f9 100644 --- a/common/serialization/subtitles/subtitles.cpp +++ b/common/serialization/subtitles/subtitles.cpp @@ -65,7 +65,7 @@ const std::unordered_map> locale_lookup = std::string lookup_locale_code(const GameVersion game_version, const int language_id) { if (locale_lookup.find(game_version) == locale_lookup.end() || - locale_lookup.at(game_version).size() < language_id) { + (int)locale_lookup.at(game_version).size() < language_id) { return ""; } return locale_lookup.at(game_version).at(language_id); diff --git a/common/serialization/subtitles/subtitles_v1.cpp b/common/serialization/subtitles/subtitles_v1.cpp index 0ba0da7d1..3e0d4062e 100644 --- a/common/serialization/subtitles/subtitles_v1.cpp +++ b/common/serialization/subtitles/subtitles_v1.cpp @@ -132,7 +132,7 @@ std::pair convert_v1_to_v2( } else { if (v1_lines_file.speakers.find(line_meta.speaker) == v1_lines_file.speakers.end() || v1_lines_file.hints.find(hint_name) == v1_lines_file.hints.end() || - line_idx >= v1_lines_file.hints.at(hint_name).size()) { + line_idx >= (int)v1_lines_file.hints.at(hint_name).size()) { lg::warn( "{} Couldn't find {} in line file, or line list is too small, or speaker could not " "be resolved {}!", @@ -145,7 +145,7 @@ std::pair convert_v1_to_v2( } } // Verify we added the amount of lines we expected to - if (lines_added != hint_info.lines.size()) { + if (lines_added != (int)hint_info.lines.size()) { throw std::runtime_error( fmt::format("Hint: '{}' has a mismatch in metadata lines vs text lines. Expected {} " "only added {} lines", @@ -222,7 +222,7 @@ GameSubtitlePackage read_json_files_v1(const GameSubtitleDefinitionFile& file_in } } -SubtitleMetadataFileV1 dump_bank_meta_v1(const GameVersion game_version, +SubtitleMetadataFileV1 dump_bank_meta_v1(const GameVersion /*game_version*/, std::shared_ptr bank) { auto meta_file = SubtitleMetadataFileV1(); for (const auto& [scene_name, scene_info] : bank->m_scenes) { diff --git a/common/serialization/subtitles/subtitles_v2.cpp b/common/serialization/subtitles/subtitles_v2.cpp index 5a0690583..ffe68e957 100644 --- a/common/serialization/subtitles/subtitles_v2.cpp +++ b/common/serialization/subtitles/subtitles_v2.cpp @@ -196,13 +196,13 @@ GameSubtitleSceneInfo GameSubtitleBank::new_scene_from_meta( // In either case, we acknowledge that there is a line, but there is no text to retrieve at that // index. if (line_meta.merge || (relevant_lines.find(scene_name) != relevant_lines.end() && - relevant_lines.at(scene_name).size() > line_idx && + (int)relevant_lines.at(scene_name).size() > line_idx && relevant_lines.at(scene_name).at(line_idx).empty())) { new_scene.m_lines.push_back({"", line_meta}); lines_added++; } else if (m_speakers.find(line_meta.speaker) == m_speakers.end() || relevant_lines.find(scene_name) == relevant_lines.end() || - line_idx >= relevant_lines.at(scene_name).size()) { + line_idx >= (int)relevant_lines.at(scene_name).size()) { lg::warn( "{} Couldn't find {} in line file, or line list is too small, or speaker could not " "be resolved {}!", @@ -290,6 +290,7 @@ SubtitleMetadataFile dump_bank_meta_v2(const GameVersion game_version, std::shared_ptr bank) { const auto dump_with_duplicates = dump_language_with_duplicates_from_base(game_version, bank->m_lang_id); + (void)dump_with_duplicates; auto meta_file = SubtitleMetadataFile(); for (const auto& [scene_name, scene_info] : bank->m_scenes) { // Avoid dumping duplicates diff --git a/common/serialization/subtitles/subtitles_v2.h b/common/serialization/subtitles/subtitles_v2.h index 5b973d53f..2cdffc1a3 100644 --- a/common/serialization/subtitles/subtitles_v2.h +++ b/common/serialization/subtitles/subtitles_v2.h @@ -89,7 +89,7 @@ struct GameSubtitleSceneInfo { return false; } // Check each line - for (int i = 0; i < m_lines.size(); i++) { + for (size_t i = 0; i < m_lines.size(); i++) { if (m_lines.at(i).text != other.m_lines.at(i).text) { return false; } @@ -102,7 +102,7 @@ struct GameSubtitleSceneInfo { return false; } // Check each line's metadata - for (int i = 0; i < m_lines.size(); i++) { + for (size_t i = 0; i < m_lines.size(); i++) { if (m_lines.at(i).metadata != other.m_lines.at(i).metadata) { return false; } diff --git a/common/sqlite/sqlite.cpp b/common/sqlite/sqlite.cpp index 41e1005ab..13dd36040 100644 --- a/common/sqlite/sqlite.cpp +++ b/common/sqlite/sqlite.cpp @@ -34,7 +34,7 @@ sqlite::GenericResponse sqlite::SQLiteDatabase::run_query(const std::string& sql const auto rc = sqlite3_exec( m_db.value().get(), sql.data(), - [](void* data, int argc, char** argv, char** azColName) { + [](void* data, int argc, char** argv, char** /*azColName*/) { GenericResponse* resp = static_cast(data); std::vector row = {}; for (int i = 0; i < argc; i++) { diff --git a/common/texture/texture_slots.cpp b/common/texture/texture_slots.cpp new file mode 100644 index 000000000..4849fb9ec --- /dev/null +++ b/common/texture/texture_slots.cpp @@ -0,0 +1,31 @@ +#include "texture_slots.h" + +namespace { +std::vector jak2_slots = { + "jakbsmall-eyebrow", + "jakbsmall-face", + "jakbsmall-finger", + "jakbsmall-hair", + "jak-orig-arm-formorph", + "jak-orig-eyebrow-formorph", + "jak-orig-eyelid-formorph", + "jak-orig-finger-formorph", + "jakb-facelft", + "jakb-facert", + "jakb-hairtrans", + "jakb-eyelid", + "jakb-finger", + "jakb-eyebrow", + //"kor-eyeeffect-formorph", + //"kor-hair-formorph", + //"kor-head-formorph", + //"kor-head-formorph-noreflect", + //"kor-lowercaps-formorph", + //"kor-uppercaps-formorph", +}; + +} + +const std::vector& jak2_animated_texture_slots() { + return jak2_slots; +} diff --git a/common/texture/texture_slots.h b/common/texture/texture_slots.h new file mode 100644 index 000000000..529cd2e19 --- /dev/null +++ b/common/texture/texture_slots.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +const std::vector& jak2_animated_texture_slots(); diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 2332feade..b35dc7ede 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -16,6 +16,7 @@ #include "common/link_types.h" #include "common/log/log.h" +#include "common/texture/texture_slots.h" #include "common/util/BinaryReader.h" #include "common/util/BitUtils.h" #include "common/util/FileUtil.h" @@ -698,7 +699,9 @@ void ObjectFileDB::find_and_write_scripts(const fs::path& output_dir) { lg::info(" Total {:.3f} ms", timer.getMs()); } -std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& output_path) { +std::string ObjectFileDB::process_tpages(TextureDB& tex_db, + const fs::path& output_path, + const Config& cfg) { lg::info("- Finding textures in tpages..."); std::string tpage_string = "tpage-"; int total = 0, success = 0; @@ -706,10 +709,25 @@ std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& outp u64 total_px = 0; Timer timer; + std::vector animated_slots; + switch (m_version) { + case GameVersion::Jak1: // no animated + break; + case GameVersion::Jak2: + animated_slots = jak2_animated_texture_slots(); + break; + default: + ASSERT_NOT_REACHED(); + } + + for (size_t i = 0; i < animated_slots.size(); i++) { + tex_db.animated_tex_output_to_anim_slot[animated_slots[i]] = i; + } + std::string result; for_each_obj([&](ObjectFileData& data) { if (data.name_in_dgo.substr(0, tpage_string.length()) == tpage_string) { - auto statistics = process_tpage(data, tex_db, output_path); + auto statistics = process_tpage(data, tex_db, output_path, cfg.animated_textures); total += statistics.total_textures; success += statistics.successful_textures; total_px += statistics.num_px; diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index 33823017c..977070b2c 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -247,7 +247,7 @@ class ObjectFileDB { const std::vector& imports, const std::unordered_set& skip_functions); - std::string process_tpages(TextureDB& tex_db, const fs::path& output_path); + std::string process_tpages(TextureDB& tex_db, const fs::path& output_path, const Config& cfg); std::string process_game_count_file(); std::string process_game_text_files(const Config& cfg); std::string process_all_spool_subtitles(const Config& cfg, const fs::path& image_out); diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 8ed3eee26..8c1618f0f 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -267,6 +267,11 @@ Config make_config_via_json(nlohmann::json& json) { config.levels_to_extract = inputs_json.at("levels_to_extract").get>(); config.levels_extract = json.at("levels_extract").get(); + if (inputs_json.contains("animated_textures")) { + config.animated_textures = + inputs_json.at("animated_textures").get>(); + } + auto art_info_json = read_json_file_from_config(json, "art_info_file"); config.art_groups_by_file = art_info_json.at("files").get>(); diff --git a/decompiler/config.h b/decompiler/config.h index 470aafe38..f67303ede 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -159,6 +159,8 @@ struct Config { std::unordered_map bad_format_strings; + std::unordered_set animated_textures; + std::vector levels_to_extract; bool levels_extract; diff --git a/decompiler/config/jak2/ntsc_v1/inputs.jsonc b/decompiler/config/jak2/ntsc_v1/inputs.jsonc index 1d0fba5b4..09b22bc45 100644 --- a/decompiler/config/jak2/ntsc_v1/inputs.jsonc +++ b/decompiler/config/jak2/ntsc_v1/inputs.jsonc @@ -443,6 +443,76 @@ "streamed_audio_file_names": [], + // Textures that should be extracted in a special way for use in animated textures. + "animated_textures": [ + // Dark Jak (small gameplay model) + "jakbsmall-eyebrow", + "jakbsmall-eyebrow-norm", + "jakbsmall-eyebrow-dark", + "jakbsmall-face", + "jakbsmall-face-norm", + "jakbsmall-face-dark", + "jakbsmall-finger", + "jakbsmall-finger-norm", + "jakbsmall-finger-dark", + "jakbsmall-hair", + "jakbsmall-hair-norm", + "jakbsmall-hair-dark", + + // Prison Jak + "jak-orig-arm-formorph", + "jak-orig-arm-formorph-start", + "jak-orig-arm-formorph-end", + "jak-orig-eyebrow-formorph", + "jak-orig-eyebrow-formorph-start", + "jak-orig-eyebrow-formorph-end", + "jak-orig-eyelid-formorph", + "jak-orig-eyelid-formorph-start", + "jak-orig-eyelid-formorph-end", + "jak-orig-finger-formorph", + "jak-orig-finger-formorph-start", + "jak-orig-finger-formorph-end", + "jakb-facelft", + "jakb-facelft-norm", + "jakb-facelft-dark", + "jakb-facert", + "jakb-facert-norm", + "jakb-facert-dark", + "jakb-hairtrans", + "jakb-hairtrans-norm", + "jakb-hairtrans-dark", + + // Oracle Jak (and nest jak) + "jakb-eyebrow", + "jakb-eyebrow-norm", + "jakb-eyebrow-dark", + "jakb-eyelid", + "jakb-eyelid-norm", + "jakb-eyelid-dark", + "jakb-finger", + "jakb-finger-norm", + "jakb-finger-dark" + + // "kor-eyeeffect-formorph", + // "kor-eyeeffect-formorph-start", + // "kor-eyeeffect-formorph-end", + // "kor-hair-formorph", + // "kor-hair-formorph-start", + // "kor-hair-formorph-end", + // "kor-head-formorph", + // "kor-head-formorph-start", + // "kor-head-formorph-end", + // "kor-head-formorph-noreflect", + // "kor-head-formorph-noreflect-start", + // "kor-head-formorph-noreflect-end", + // "kor-lowercaps-formorph", + // "kor-lowercaps-formorph-start", + // "kor-lowercaps-formorph-end", + // "kor-uppercaps-formorph", + // "kor-uppercaps-formorph-start", + // "kor-uppercaps-formorph-end" + ], + "levels_to_extract": [ "ATE.DGO", "ATO.DGO", diff --git a/decompiler/data/TextureDB.cpp b/decompiler/data/TextureDB.cpp index 720d2af0f..d4568d6e5 100644 --- a/decompiler/data/TextureDB.cpp +++ b/decompiler/data/TextureDB.cpp @@ -51,6 +51,48 @@ void TextureDB::add_texture(u32 tpage, } } +void TextureDB::add_index_texture(u32 tpage, + u32 texid, + const std::vector& index_data, + const std::array, 256>& clut, + u16 w, + u16 h, + const std::string& tex_name, + const std::string& tpage_name, + const std::vector& level_names) { + auto existing_tpage_name = tpage_names.find(tpage); + if (existing_tpage_name == tpage_names.end()) { + tpage_names[tpage] = tpage_name; + } else { + ASSERT(existing_tpage_name->second == tpage_name); + } + + u32 combo_id = (tpage << 16) | texid; + auto existing_tex = index_textures_by_combo_id.find(combo_id); + if (existing_tex != index_textures_by_combo_id.end()) { + ASSERT(existing_tex->second.name == tex_name); + ASSERT(existing_tex->second.w == w); + ASSERT(existing_tex->second.h == h); + ASSERT(existing_tex->second.index_data == index_data); + ASSERT(existing_tex->second.combo_id == combo_id); + ASSERT(existing_tex->second.color_table == clut); + ASSERT(existing_tex->second.tpage_name == tpage_name); + for (auto& ln : level_names) { + existing_tex->second.level_names.push_back(ln); + } + } else { + auto& new_tex = index_textures_by_combo_id[combo_id]; + new_tex.index_data = index_data; + new_tex.color_table = clut; + new_tex.name = tex_name; + new_tex.w = w; + new_tex.h = h; + new_tex.tpage_name = tpage_name; + new_tex.combo_id = combo_id; + new_tex.level_names = level_names; + } +} + void TextureDB::replace_textures(const fs::path& path) { fs::path base_path(path); for (auto& tex : textures) { diff --git a/decompiler/data/TextureDB.h b/decompiler/data/TextureDB.h index e7de20405..1c293ed61 100644 --- a/decompiler/data/TextureDB.h +++ b/decompiler/data/TextureDB.h @@ -7,6 +7,7 @@ #include #include "common/common_types.h" +#include "common/custom_data/Tfrag3Data.h" #include "common/util/FileUtil.h" namespace decompiler { @@ -24,6 +25,11 @@ struct TextureDB { std::unordered_map tpage_names; std::unordered_map> texture_ids_per_level; + // special textures for animation. + std::map index_textures_by_combo_id; + + std::unordered_map animated_tex_output_to_anim_slot; + void add_texture(u32 tpage, u32 texid, const std::vector& data, @@ -35,6 +41,16 @@ struct TextureDB { u32 num_mips, u32 dest); + void add_index_texture(u32 tpage, + u32 texid, + const std::vector& index_data, + const std::array, 256>& clut, + u16 w, + u16 h, + const std::string& tex_name, + const std::string& tpage_name, + const std::vector& level_names); + void replace_textures(const fs::path& path); std::string generate_texture_dest_adjustment_table() const; diff --git a/decompiler/data/tpage.cpp b/decompiler/data/tpage.cpp index 1d8e25a90..b9734db99 100644 --- a/decompiler/data/tpage.cpp +++ b/decompiler/data/tpage.cpp @@ -16,6 +16,7 @@ #include "tpage.h" +#include "common/log/log.h" #include "common/texture/texture_conversion.h" #include "common/util/FileUtil.h" #include "common/versions/versions.h" @@ -431,7 +432,8 @@ TexturePage read_texture_page(ObjectFileData& data, */ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db, - const fs::path& output_path) { + const fs::path& output_path, + const std::unordered_set& animated_textures) { TPageResultStats stats; auto& words = data.linked_data.words_by_seg.at(0); const auto& level_names = data.dgo_names; @@ -451,6 +453,12 @@ TPageResultStats process_tpage(ObjectFileData& data, // Read the texture_page struct TexturePage texture_page = read_texture_page(data, words, 0, end_of_texture_page); + bool ignore_animated = texture_page.name == "sewesc-vis-pris"; + if (ignore_animated) { + lg::warn( + "Ignoring animated textures from this tpage ({}) because of weird jakbsmall-finger issue", + texture_page.name); + } auto texture_dump_dir = output_path / texture_page.name; file_util::create_dir_if_needed(texture_dump_dir); @@ -494,9 +502,57 @@ TPageResultStats process_tpage(ObjectFileData& data, stats.total_textures++; stats.num_px += tex.w * tex.h; - if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT32)) { - // this is the only supported texture format for now. + if (animated_textures.count(tex.name) && !ignore_animated) { + switch (tex.psm) { + case int(PSM::PSMT8): + ASSERT(tex.clutpsm == int(CPSM::PSMCT32)); + { + // will store output pixels, index (u8) + std::vector index_out; + // width is like the TEX0 register, in 64 texel units. + // not sure what the other widths are yet. + int read_width = 64 * tex.width[0]; + + // loop over pixels in output texture image + for (int y = 0; y < tex.h; y++) { + for (int x = 0; x < tex.w; x++) { + // read as the PSMT8 type. The dest field tells us a block offset. + auto addr8 = psmt8_addr(x, y, read_width) + tex.dest[0] * 256; + u8 value = vram[addr8]; + index_out.push_back(value); + } + } + std::array, 256> unscrambled_clut; + for (int i = 0; i < 256; i++) { + u32 clut_chunk = i / 16; + u32 off_in_chunk = i % 16; + u8 clx = 0, cly = 0; + if (clut_chunk & 1) { + clx = 8; + } + cly = (clut_chunk >> 1) * 2; + if (off_in_chunk >= 8) { + off_in_chunk -= 8; + cly++; + } + clx += off_in_chunk; + u32 clut_addr = psmct32_addr(clx, cly, 64) + tex.clutdest * 256; + memcpy(&unscrambled_clut[i], vram.data() + clut_addr, 4); + } + + // lg::warn("Adding index texture {} from {}\n", texture_page.name, tex.name); + texture_db.add_index_texture(texture_page.id, tex_id, index_out, unscrambled_clut, + tex.w, tex.h, tex.name, texture_page.name, level_names); + stats.successful_textures++; + } + break; + default: + lg::die("Animated texture {} format {}\n", tex.name, tex.psm); + } + } + + if (tex.psm == int(PSM::PSMT8) && tex.clutpsm == int(CPSM::PSMCT32)) { // will store output pixels, rgba (8888) std::vector out; diff --git a/decompiler/data/tpage.h b/decompiler/data/tpage.h index b442743dd..d9592f71a 100644 --- a/decompiler/data/tpage.h +++ b/decompiler/data/tpage.h @@ -1,4 +1,6 @@ #pragma once +#include +#include #include "decompiler/data/TextureDB.h" @@ -13,5 +15,6 @@ struct TPageResultStats { TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db, - const fs::path& output_path); + const fs::path& output_path, + const std::unordered_set& animated_textures); } // namespace decompiler diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index 912f40460..a03c9eb6c 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -163,7 +163,7 @@ void decompile(const fs::path& iso_data_path, const std::string& data_subfolder) auto textures_out = out_folder / "textures"; file_util::create_dir_if_needed(textures_out); file_util::write_text_file(textures_out / "tpage-dir.txt", - db.process_tpages(tex_db, textures_out)); + db.process_tpages(tex_db, textures_out, config)); // texture replacements auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements"; if (fs::exists(replacements_path)) { diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 028d1fc3d..0ce001da3 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -85,7 +85,7 @@ void add_all_textures_from_level(tfrag3::Level& lev, 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 = new_tex.debug_tpage_name + tex.name; + new_tex.debug_name = tex.name; new_tex.data = tex.rgba_bytes; new_tex.combo_id = id; new_tex.load_to_pool = true; @@ -241,6 +241,11 @@ 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); + // 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); + } + Serializer ser; tfrag_level.serialize(ser); auto compressed = diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index 128a050b2..2075c873e 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -726,22 +726,22 @@ std::string debug_dump_to_ply(const std::vector& draws, return result; } -int find_or_add_texture_to_level(tfrag3::Level& out, +s32 find_or_add_texture_to_level(tfrag3::Level& out, const TextureDB& tex_db, const std::string& debug_name, u32 pc_combo_tex_id, const MercCtrlHeader& hdr, u8* eye_out, GameVersion version) { - u32 idx_in_level_texture = UINT32_MAX; - for (u32 i = 0; i < out.textures.size(); i++) { + s32 idx_in_level_texture = INT32_MAX; + for (s32 i = 0; i < (int)out.textures.size(); i++) { if (out.textures[i].combo_id == pc_combo_tex_id) { idx_in_level_texture = i; break; } } - if (idx_in_level_texture == UINT32_MAX) { + if (idx_in_level_texture == INT32_MAX) { // not added to level, add it auto tex_it = tex_db.textures.find(pc_combo_tex_id); if (tex_it == tex_db.textures.end()) { @@ -792,6 +792,16 @@ int find_or_add_texture_to_level(tfrag3::Level& out, } } + // check anim output + 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/main.cpp b/decompiler/main.cpp index 2db59e4d9..e1342e048 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -268,7 +268,7 @@ int main(int argc, char** argv) { if (config.process_tpages || config.levels_extract) { auto textures_out = out_folder / "textures"; file_util::create_dir_if_needed(textures_out); - auto result = db.process_tpages(tex_db, textures_out); + auto result = db.process_tpages(tex_db, textures_out, config); if (!result.empty() && config.process_tpages) { file_util::write_text_file(textures_out / "tpage-dir.txt", result); file_util::write_text_file(textures_out / "tex-remap.txt", diff --git a/game/graphics/opengl_renderer/BlitDisplays.cpp b/game/graphics/opengl_renderer/BlitDisplays.cpp index 48eb6b10f..dbbfcdd87 100644 --- a/game/graphics/opengl_renderer/BlitDisplays.cpp +++ b/game/graphics/opengl_renderer/BlitDisplays.cpp @@ -2,8 +2,7 @@ #include "common/log/log.h" -#include "game/graphics/opengl_renderer/OpenGLRenderer.h" -#include "game/graphics/pipelines/opengl.h" +#include "game/graphics/opengl_renderer/Fbo.h" BlitDisplays::BlitDisplays(const std::string& name, int my_id) : BucketRenderer(name, my_id) {} diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 78c03e907..773d76431 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -7,7 +7,6 @@ #include "game/graphics/opengl_renderer/Profiler.h" #include "game/graphics/opengl_renderer/Shader.h" -#include "game/graphics/opengl_renderer/TextureAnimator.h" #include "game/graphics/opengl_renderer/buckets.h" #include "game/graphics/opengl_renderer/loader/Loader.h" #include "game/graphics/texture/TexturePool.h" @@ -32,7 +31,6 @@ struct SharedRenderState { ShaderLibrary shaders; std::shared_ptr texture_pool; std::shared_ptr loader; - std::shared_ptr texture_animator; u32 buckets_base = 0; // address of buckets array. u32 next_bucket = 0; // address of next bucket that we haven't started rendering in buckets diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp index 8672fa2f0..887f94077 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.cpp +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -140,6 +140,13 @@ void DirectRenderer::reset_state() { m_stats = {}; } +void DirectRenderer::init_shaders(ShaderLibrary& sl) { + auto id = sl[ShaderId::DIRECT_BASIC_TEXTURED].id(); + m_uniforms.alpha_min = glGetUniformLocation(id, "alpha_min"); + m_uniforms.alpha_max = glGetUniformLocation(id, "alpha_max"); + m_uniforms.normal_shader_id = id; +} + void DirectRenderer::draw_debug_window() { ImGui::Checkbox("Wireframe", &m_debug_state.wireframe); ImGui::SameLine(); @@ -252,8 +259,40 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil game_height[render_state->version], viewport_size[2], viewport_size[3]); int draw_count = 0; - glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count); - draw_count++; + int num_tris = 0; + + if (m_test_state_needs_double_draw && current_shader == m_uniforms.normal_shader_id) { + // this batch thing is a hack to make the sky in jak 2 draw correctly. + // This is the usual atest with FB_ONLY issue. + // we should check what pcsx2 does + int n_batch = m_prim_buffer.vert_count; + if (n_batch > 50 && n_batch < 700 && (n_batch % 2) == 0) { + n_batch = n_batch / 2; + } else { + // printf("not splitting batch %d\n", n_batch); + } + int offset = 0; + while (offset < m_prim_buffer.vert_count) { + glDepthMask(GL_TRUE); + glUniform1f(m_uniforms.alpha_min, m_double_draw_aref); + glUniform1f(m_uniforms.alpha_max, 10); + glDrawArrays(GL_TRIANGLES, offset, n_batch); + glDepthMask(GL_FALSE); + glUniform1f(m_uniforms.alpha_min, -10); + glUniform1f(m_uniforms.alpha_max, m_double_draw_aref); + glDrawArrays(GL_TRIANGLES, offset, n_batch); + offset += n_batch; + draw_count += 2; + num_tris += n_batch / 3; + } + m_test_state_needs_double_draw = false; + m_test_state_needs_gl_update = true; + m_prim_gl_state_needs_gl_update = true; + } else { + glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count); + num_tris += m_prim_buffer.vert_count / 3; + draw_count++; + } if (m_debug_state.wireframe) { render_state->shaders[ShaderId::DEBUG_RED].activate(); @@ -268,10 +307,9 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil glActiveTexture(GL_TEXTURE0); glBindVertexArray(0); - int n_tris = draw_count * (m_prim_buffer.vert_count / 3); - prof.add_tri(n_tris); + prof.add_tri(num_tris); prof.add_draw_call(draw_count); - m_stats.triangles += n_tris; + m_stats.triangles += num_tris; m_stats.draw_calls += draw_count; m_prim_buffer.vert_count = 0; } @@ -280,14 +318,16 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) { // currently gouraud is handled in setup. const auto& state = m_prim_gl_state; if (state.texture_enable) { - float alpha_reject = 0.0; + float alpha_min = 0.0; + float alpha_max = 10; if (m_test_state.alpha_test_enable) { switch (m_test_state.alpha_test) { case GsTest::AlphaTest::ALWAYS: break; case GsTest::AlphaTest::GEQUAL: case GsTest::AlphaTest::GREATER: // todo - alpha_reject = m_test_state.aref / 128.f; + alpha_min = m_test_state.aref / 128.f; + m_double_draw_aref = alpha_min; break; case GsTest::AlphaTest::NEVER: break; @@ -298,8 +338,11 @@ void DirectRenderer::update_gl_prim(SharedRenderState* render_state) { render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].activate(); glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(), - "alpha_reject"), - alpha_reject); + "alpha_min"), + alpha_min); + glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(), + "alpha_max"), + alpha_max); glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(), "color_mult"), m_ogl.color_mult); @@ -475,6 +518,14 @@ void DirectRenderer::update_gl_test() { bool alpha_trick_to_disable = m_test_state.alpha_test_enable && m_test_state.alpha_test == GsTest::AlphaTest::NEVER && m_test_state.afail == GsTest::AlphaFail::FB_ONLY; + + if (m_test_state.afail == GsTest::AlphaFail::FB_ONLY || + m_test_state.afail == GsTest::AlphaFail::RGB_ONLY) { + m_test_state_needs_double_draw = true; + } else { + m_test_state_needs_double_draw = false; + } + if (state.depth_writes && !alpha_trick_to_disable) { glDepthMask(GL_TRUE); } else { diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h index e6aac4dc4..6e467f5f2 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.h +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -21,6 +21,7 @@ class DirectRenderer : public BucketRenderer { public: DirectRenderer(const std::string& name, int my_id, int batch_size); + void init_shaders(ShaderLibrary& sl) override; ~DirectRenderer(); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; virtual void pre_render() {} @@ -289,6 +290,11 @@ class DirectRenderer : public BucketRenderer { float alpha_mult = 1.0; } m_ogl; + struct { + GLint alpha_min, alpha_max; + GLint normal_shader_id = -1; + } m_uniforms; + struct { bool disable_texture = false; bool wireframe = false; @@ -314,6 +320,8 @@ class DirectRenderer : public BucketRenderer { bool m_prim_gl_state_needs_gl_update = true; bool m_test_state_needs_gl_update = true; + bool m_test_state_needs_double_draw = false; + float m_double_draw_aref = 0; bool m_blend_state_needs_gl_update = true; struct SpriteMode { diff --git a/game/graphics/opengl_renderer/Fbo.h b/game/graphics/opengl_renderer/Fbo.h new file mode 100644 index 000000000..a372c3ca9 --- /dev/null +++ b/game/graphics/opengl_renderer/Fbo.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "game/graphics/pipelines/opengl.h" + +struct Fbo { + bool valid = false; // do we have an OpenGL fbo_id? + GLuint fbo_id = -1; + + // optional rgba/zbuffer/stencil data. + std::optional tex_id; + std::optional zbuf_stencil_id; + + bool multisampled = false; + int multisample_count = 0; // Should be 1 if multisampled is disabled + + bool is_window = false; + int width = 640; + int height = 480; + + // Does this fbo match the given format? MSAA = 1 will accept a normal buffer, or a multisample 1x + bool matches(int w, int h, int msaa) const { + int effective_msaa = multisampled ? multisample_count : 1; + return valid && width == w && height == h && effective_msaa == msaa; + } + + bool matches(const Fbo& other) const { + return matches(other.width, other.height, other.multisample_count); + } + + // Free opengl resources, if we have any. + void clear() { + if (valid) { + glDeleteFramebuffers(1, &fbo_id); + fbo_id = -1; + + if (tex_id) { + glDeleteTextures(1, &tex_id.value()); + tex_id.reset(); + } + + if (zbuf_stencil_id) { + glDeleteRenderbuffers(1, &zbuf_stencil_id.value()); + zbuf_stencil_id.reset(); + } + + valid = false; + } + } +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index aeb6da33a..805d431fe 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -85,7 +85,25 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool, lg::debug("OpenGL context information: {}", (const char*)glGetString(GL_VERSION)); - m_merc2 = std::make_shared(m_render_state.shaders); + const tfrag3::Level* common_level = nullptr; + { + auto p = scoped_prof("load-common"); + common_level = &m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME"); + } + + // initialize all renderers + switch (m_version) { + case GameVersion::Jak1: + break; + case GameVersion::Jak2: + m_texture_animator = std::make_shared(m_render_state.shaders, common_level); + break; + default: + ASSERT(false); + } + + m_merc2 = std::make_shared(m_render_state.shaders, + m_texture_animator ? m_texture_animator->slots() : nullptr); m_generic2 = std::make_shared(m_render_state.shaders); // initialize all renderers @@ -95,7 +113,6 @@ OpenGLRenderer::OpenGLRenderer(std::shared_ptr texture_pool, init_bucket_renderers_jak1(); break; case GameVersion::Jak2: - m_render_state.texture_animator = std::make_shared(); init_bucket_renderers_jak2(); break; default: @@ -112,7 +129,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer("vis", BucketCategory::OTHER, BucketId::BUCKET_2); init_bucket_renderer("blit", BucketCategory::OTHER, BucketId::BUCKET_3); init_bucket_renderer("tex-lcom-sky-pre", BucketCategory::TEX, - BucketId::TEX_LCOM_SKY_PRE); + BucketId::TEX_LCOM_SKY_PRE, m_texture_animator); init_bucket_renderer("sky-draw", BucketCategory::OTHER, BucketId::SKY_DRAW, 1024); init_bucket_renderer("ocean-mid-far", BucketCategory::OCEAN, BucketId::OCEAN_MID_FAR); @@ -121,7 +138,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { #define GET_BUCKET_ID_FOR_LIST(bkt1, bkt2, idx) ((int)(bkt1) + ((int)(bkt2) - (int)(bkt1)) * (idx)) init_bucket_renderer( fmt::format("tex-l{}-tfrag", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_TFRAG, BucketId::TEX_L1_TFRAG, i), + m_texture_animator); 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), @@ -143,7 +161,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tex-l{}-shrub", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_SHRUB, BucketId::TEX_L1_SHRUB, i), + m_texture_animator); init_bucket_renderer( fmt::format("shrub-l{}-shrub", i), BucketCategory::SHRUB, GET_BUCKET_ID_FOR_LIST(BucketId::SHRUB_L0_SHRUB, BucketId::SHRUB_L1_SHRUB, i)); @@ -157,7 +176,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tex-l{}-alpha", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_ALPHA, BucketId::TEX_L1_ALPHA, i), + m_texture_animator); 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), @@ -180,7 +200,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tex-l{}-pris", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS, BucketId::TEX_L1_PRIS, i), + m_texture_animator); init_bucket_renderer( fmt::format("merc-l{}-pris", i), BucketCategory::MERC, GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS, BucketId::MERC_L1_PRIS, i), m_merc2); @@ -191,7 +212,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tex-l{}-pris2", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_PRIS2, BucketId::TEX_L1_PRIS2, i), + m_texture_animator); init_bucket_renderer( fmt::format("merc-l{}-pris2", i), BucketCategory::MERC, GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_PRIS2, BucketId::MERC_L1_PRIS2, i), m_merc2); @@ -202,7 +224,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer( fmt::format("tex-l{}-water", i), BucketCategory::TEX, - GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i)); + GET_BUCKET_ID_FOR_LIST(BucketId::TEX_L0_WATER, BucketId::TEX_L1_WATER, i), + m_texture_animator); init_bucket_renderer( fmt::format("merc-l{}-water", i), BucketCategory::MERC, GET_BUCKET_ID_FOR_LIST(BucketId::MERC_L0_WATER, BucketId::MERC_L1_WATER, i), m_merc2); @@ -226,12 +249,12 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { } // 180 init_bucket_renderer("tex-lcom-tfrag", BucketCategory::TEX, - BucketId::TEX_LCOM_TFRAG); + BucketId::TEX_LCOM_TFRAG, m_texture_animator); init_bucket_renderer("merc-lcom-tfrag", BucketCategory::MERC, BucketId::MERC_LCOM_TFRAG, m_merc2); // 190 init_bucket_renderer("tex-lcom-shrub", BucketCategory::TEX, - BucketId::TEX_LCOM_SHRUB); + BucketId::TEX_LCOM_SHRUB, m_texture_animator); init_bucket_renderer("merc-lcom-shrub", BucketCategory::MERC, BucketId::MERC_LCOM_SHRUB, m_merc2); init_bucket_renderer("gmerc-lcom-tfrag", BucketCategory::GENERIC, @@ -240,30 +263,30 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { init_bucket_renderer("shadow", BucketCategory::OTHER, BucketId::SHADOW); // 220 init_bucket_renderer("tex-lcom-pris", BucketCategory::TEX, - BucketId::TEX_LCOM_PRIS); + BucketId::TEX_LCOM_PRIS, m_texture_animator); init_bucket_renderer("merc-lcom-pris", BucketCategory::MERC, BucketId::MERC_LCOM_PRIS, m_merc2); init_bucket_renderer("tex-lcom-water", BucketCategory::TEX, - BucketId::TEX_LCOM_WATER); + BucketId::TEX_LCOM_WATER, m_texture_animator); init_bucket_renderer("merc-lcom-water", BucketCategory::MERC, BucketId::MERC_LCOM_WATER, m_merc2); init_bucket_renderer("tex-lcom-sky-post", BucketCategory::TEX, - BucketId::TEX_LCOM_SKY_POST); + BucketId::TEX_LCOM_SKY_POST, m_texture_animator); // 310 init_bucket_renderer("ocean-near", BucketCategory::OCEAN, BucketId::OCEAN_NEAR); init_bucket_renderer("tex-all-sprite", BucketCategory::TEX, - BucketId::TEX_ALL_SPRITE); + BucketId::TEX_ALL_SPRITE, m_texture_animator); init_bucket_renderer("particles", BucketCategory::SPRITE, BucketId::PARTICLES); init_bucket_renderer("shadow2", BucketCategory::OTHER, BucketId::SHADOW2); init_bucket_renderer("effects", BucketCategory::OTHER, BucketId::EFFECTS, m_generic2, Generic2::Mode::LIGHTNING); init_bucket_renderer("tex-all-warp", BucketCategory::TEX, - BucketId::TEX_ALL_WARP); + BucketId::TEX_ALL_WARP, m_texture_animator); init_bucket_renderer("warp", BucketCategory::GENERIC, BucketId::GMERC_WARP, m_generic2); init_bucket_renderer("debug-no-zbuf1", BucketCategory::OTHER, BucketId::DEBUG_NO_ZBUF1, 0x8000); init_bucket_renderer("tex-all-map", BucketCategory::TEX, - BucketId::TEX_ALL_MAP); + BucketId::TEX_ALL_MAP, m_texture_animator); // 320 init_bucket_renderer("progress", BucketCategory::OTHER, BucketId::PROGRESS, 0x1000); @@ -295,10 +318,8 @@ void OpenGLRenderer::init_bucket_renderers_jak2() { m_jak2_eye_renderer->init_shaders(m_render_state.shaders); m_jak2_eye_renderer->init_textures(*m_render_state.texture_pool, GameVersion::Jak2); } - - auto p = scoped_prof("load-common"); - m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME"); } + /*! * Construct bucket renderers. We can specify different renderers for different buckets */ @@ -331,7 +352,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { //----------------------- // 5 : TFRAG_TEX_LEVEL0 init_bucket_renderer("l0-tfrag-tex", BucketCategory::TEX, - BucketId::TFRAG_TEX_LEVEL0); + 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); @@ -353,7 +374,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { //----------------------- // 12 : TFRAG_TEX_LEVEL1 init_bucket_renderer("l1-tfrag-tex", BucketCategory::TEX, - BucketId::TFRAG_TEX_LEVEL1); + 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); @@ -375,7 +396,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { //----------------------- // 19 : SHRUB_TEX_LEVEL0 init_bucket_renderer("l0-shrub-tex", BucketCategory::TEX, - BucketId::SHRUB_TEX_LEVEL0); + BucketId::SHRUB_TEX_LEVEL0, m_texture_animator); // 20 : SHRUB_NORMAL_LEVEL0 init_bucket_renderer("l0-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL0); // 21 : ??? @@ -391,7 +412,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { //----------------------- // 25 : SHRUB_TEX_LEVEL1 init_bucket_renderer("l1-shrub-tex", BucketCategory::TEX, - BucketId::SHRUB_TEX_LEVEL1); + BucketId::SHRUB_TEX_LEVEL1, m_texture_animator); // 26 : SHRUB_NORMAL_LEVEL1 init_bucket_renderer("l1-shrub", BucketCategory::SHRUB, BucketId::SHRUB_NORMAL_LEVEL1); // 27 : ??? @@ -406,7 +427,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 0 alpha texture //----------------------- init_bucket_renderer("l0-alpha-tex", BucketCategory::TEX, - BucketId::ALPHA_TEX_LEVEL0); // 31 + 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 @@ -423,7 +444,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 1 alpha texture //----------------------- init_bucket_renderer("l1-alpha-tex", BucketCategory::TEX, - BucketId::ALPHA_TEX_LEVEL1); // 38 + 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 @@ -448,7 +469,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 0 pris texture //----------------------- init_bucket_renderer("l0-pris-tex", BucketCategory::TEX, - BucketId::PRIS_TEX_LEVEL0); // 48 + BucketId::PRIS_TEX_LEVEL0, m_texture_animator); // 48 init_bucket_renderer("l0-pris-merc", BucketCategory::MERC, BucketId::MERC_PRIS_LEVEL0, m_merc2); // 49 init_bucket_renderer("l0-pris-generic", BucketCategory::GENERIC, @@ -459,7 +480,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 1 pris texture //----------------------- init_bucket_renderer("l1-pris-tex", BucketCategory::TEX, - BucketId::PRIS_TEX_LEVEL1); // 51 + BucketId::PRIS_TEX_LEVEL1, m_texture_animator); // 51 init_bucket_renderer("l1-pris-merc", BucketCategory::MERC, BucketId::MERC_PRIS_LEVEL1, m_merc2); // 52 init_bucket_renderer("l1-pris-generic", BucketCategory::GENERIC, @@ -481,7 +502,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 0 water texture //----------------------- init_bucket_renderer("l0-water-tex", BucketCategory::TEX, - BucketId::WATER_TEX_LEVEL0); // 57 + BucketId::WATER_TEX_LEVEL0, m_texture_animator); // 57 init_bucket_renderer("l0-water-merc", BucketCategory::MERC, BucketId::MERC_WATER_LEVEL0, m_merc2); // 58 init_bucket_renderer("l0-water-generic", BucketCategory::GENERIC, @@ -492,7 +513,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // LEVEL 1 water texture //----------------------- init_bucket_renderer("l1-water-tex", BucketCategory::TEX, - BucketId::WATER_TEX_LEVEL1); // 60 + BucketId::WATER_TEX_LEVEL1, m_texture_animator); // 60 init_bucket_renderer("l1-water-merc", BucketCategory::MERC, BucketId::MERC_WATER_LEVEL1, m_merc2); // 61 init_bucket_renderer("l1-water-generic", BucketCategory::GENERIC, @@ -510,7 +531,7 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { // COMMON texture //----------------------- init_bucket_renderer("common-tex", BucketCategory::TEX, - BucketId::PRE_SPRITE_TEX); // 65 + BucketId::PRE_SPRITE_TEX, m_texture_animator); // 65 init_bucket_renderer("sprite", BucketCategory::SPRITE, BucketId::SPRITE); // 66 @@ -532,7 +553,6 @@ void OpenGLRenderer::init_bucket_renderers_jak1() { } sky_cpu_blender->init_textures(*m_render_state.texture_pool, m_version); sky_gpu_blender->init_textures(*m_render_state.texture_pool, m_version); - m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME"); } namespace { diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index fcd916ffd..ebacf6a7e 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -7,8 +7,10 @@ #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/CollideMeshRenderer.h" +#include "game/graphics/opengl_renderer/Fbo.h" #include "game/graphics/opengl_renderer/Profiler.h" #include "game/graphics/opengl_renderer/Shader.h" +#include "game/graphics/opengl_renderer/TextureAnimator.h" #include "game/graphics/opengl_renderer/foreground/Generic2.h" #include "game/graphics/opengl_renderer/foreground/Merc2.h" #include "game/graphics/opengl_renderer/opengl_utils.h" @@ -51,52 +53,6 @@ struct RenderOptions { bool gpu_sync = false; }; -struct Fbo { - bool valid = false; // do we have an OpenGL fbo_id? - GLuint fbo_id = -1; - - // optional rgba/zbuffer/stencil data. - std::optional tex_id; - std::optional zbuf_stencil_id; - - bool multisampled = false; - int multisample_count = 0; // Should be 1 if multisampled is disabled - - bool is_window = false; - int width = 640; - int height = 480; - - // Does this fbo match the given format? MSAA = 1 will accept a normal buffer, or a multisample 1x - bool matches(int w, int h, int msaa) const { - int effective_msaa = multisampled ? multisample_count : 1; - return valid && width == w && height == h && effective_msaa == msaa; - } - - bool matches(const Fbo& other) const { - return matches(other.width, other.height, other.multisample_count); - } - - // Free opengl resources, if we have any. - void clear() { - if (valid) { - glDeleteFramebuffers(1, &fbo_id); - fbo_id = -1; - - if (tex_id) { - glDeleteTextures(1, &tex_id.value()); - tex_id.reset(); - } - - if (zbuf_stencil_id) { - glDeleteRenderbuffers(1, &zbuf_stencil_id.value()); - zbuf_stencil_id.reset(); - } - - valid = false; - } - } -}; - /*! * Main OpenGL renderer. * This handles the glClear and all game rendering, but not actual setup, synchronization or imgui @@ -150,6 +106,7 @@ class OpenGLRenderer { std::shared_ptr m_merc2; std::shared_ptr m_generic2; + std::shared_ptr m_texture_animator; std::vector> m_bucket_renderers; std::vector m_bucket_categories; diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 16ce60ae6..12221d82b 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -125,6 +125,7 @@ ShaderLibrary::ShaderLibrary(GameVersion version) { at(ShaderId::ETIE_BASE) = {"etie_base", version}; at(ShaderId::ETIE) = {"etie", version}; at(ShaderId::SHADOW2) = {"shadow2", version}; + at(ShaderId::TEX_ANIM) = {"tex_anim", version}; for (auto& shader : m_shaders) { ASSERT_MSG(shader.okay(), "error compiling shader"); diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index c22a43ad0..53fbff637 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -58,6 +58,7 @@ enum class ShaderId { ETIE = 31, SHADOW2 = 32, DIRECT_BASIC_TEXTURED_MULTI_UNIT = 33, + TEX_ANIM = 34, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/TextureAnimator.cpp b/game/graphics/opengl_renderer/TextureAnimator.cpp index 4c064d996..1bc487435 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.cpp +++ b/game/graphics/opengl_renderer/TextureAnimator.cpp @@ -1,10 +1,1361 @@ #include "TextureAnimator.h" -void TextureAnimator::handle_texture_anim_data(DmaFollower& dma) { - auto vif0 = dma.current_tag_vifcode0(); - while (vif0.kind != VifCode::Kind::PC_PORT || vif0.immediate != 13) { - dma.read_and_advance(); - vif0 = dma.current_tag_vifcode0(); +#include "common/global_profiler/GlobalProfiler.h" +#include "common/texture/texture_slots.h" +#include "common/util/FileUtil.h" +#include "common/util/Timer.h" + +#include "game/graphics/texture/TexturePool.h" + +//#define dprintf(...) printf(__VA_ARGS__) +//#define dfmt(...) fmt::print(__VA_ARGS__) +#define dprintf(...) +#define dfmt(...) + +// 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. + +// TODO: +// clouds aren't really working right. The final operation of move rb to ba is a guess. +// then it's actually treated as a palette texture, but we don't really do this. +// This breaks the fade-out/thresholding, and likely the colors. But it still looks vaguely like +// clouds. + +/*! + * A simple list of preallocated textures by size. If a texture needs to be resized, it's faster + * to swap to a different OpenGL texture from this pool than glTexImage2D with a different size. + */ +OpenGLTexturePool::OpenGLTexturePool() { + struct Alloc { + u64 w, h, n; + }; + // list of sizes to preallocate. + for (const auto& a : std::vector{{16, 16, 5}, // + {32, 16, 1}, + {32, 32, 5}, + {32, 64, 1}, + {64, 64, 8}, + {64, 128, 4}, + {128, 128, 5}, + {256, 1, 2}, + {256, 256, 7}}) { + auto& l = textures[(a.w << 32) | a.h]; + l.resize(a.n); + glGenTextures(a.n, l.data()); + for (auto t : l) { + glBindTexture(GL_TEXTURE_2D, t); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, a.w, a.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + nullptr); + } } - dma.read_and_advance(); -} \ No newline at end of file +} + +OpenGLTexturePool::~OpenGLTexturePool() { + for (auto& [_, l] : textures) { + glDeleteTextures(l.size(), l.data()); + } +} + +GLuint OpenGLTexturePool::allocate(u64 w, u64 h) { + const auto& it = textures.find((w << 32) | h); + if (it == textures.end()) { + lg::die("OpenGLTexturePool needs entries for {} x {}", w, h); + } + + if (it->second.empty()) { + lg::die("OpenGLTexturePool needs more entries for {} x {}", w, h); + } + + auto ret = it->second.back(); + it->second.pop_back(); + return ret; +} + +void OpenGLTexturePool::free(GLuint texture, u64 w, u64 h) { + textures[(w << 32) | h].push_back(texture); +} + +const tfrag3::IndexTexture* itex_by_name(const tfrag3::Level* level, + const std::string& name, + const std::optional& level_name) { + const tfrag3::IndexTexture* ret = nullptr; + for (const auto& t : level->index_textures) { + bool match = t.name == name; + if (level_name && match) { + match = + std::find(t.level_names.begin(), t.level_names.end(), *level_name) != t.level_names.end(); + if (!match && false) { + lg::warn("rejecting {} because it wasn't in desired level {}, but was in:", t.name, + *level_name); + for (auto& l : t.level_names) { + lg::warn(" {}", l); + } + } + } + if (match) { + if (ret) { + lg::error("Multiple index textures named {}", name); + ASSERT(ret->color_table == t.color_table); + ASSERT(ret->index_data == t.index_data); + } + ret = &t; + } + } + if (!ret) { + lg::die("no index texture named {}", name); + } else { + // lg::info("got idx: {}", name); + } + return ret; +} + +int output_slot_by_idx(GameVersion version, const std::string& name) { + const std::vector* v = nullptr; + switch (version) { + case GameVersion::Jak2: + v = &jak2_animated_texture_slots(); + break; + default: + case GameVersion::Jak1: + ASSERT_NOT_REACHED(); + } + + for (size_t i = 0; i < v->size(); i++) { + if ((*v)[i] == name) { + return i; + } + } + ASSERT_NOT_REACHED(); +} + +ClutBlender::ClutBlender(const std::string& dest, + const std::vector& sources, + const std::optional& level_name, + const tfrag3::Level* level, + OpenGLTexturePool* tpool) { + m_dest = itex_by_name(level, dest, level_name); + for (const auto& sname : sources) { + m_cluts.push_back(&itex_by_name(level, sname, level_name)->color_table); + m_current_weights.push_back(0); + } + m_texture = tpool->allocate(m_dest->w, m_dest->h); + m_temp_rgba.resize(m_dest->w * m_dest->h); + + std::vector init_weights(m_current_weights.size(), 0); + init_weights.at(0) = 1.f; + run(init_weights.data()); +} + +GLuint ClutBlender::run(const float* weights) { + bool needs_run = false; + + for (size_t i = 0; i < m_current_weights.size(); i++) { + if (weights[i] != m_current_weights[i]) { + needs_run = true; + break; + } + } + + if (!needs_run) { + return m_texture; + } + + for (size_t i = 0; i < m_current_weights.size(); i++) { + m_current_weights[i] = weights[i]; + } + + for (int i = 0; i < 256; i++) { + math::Vector4f v = math::Vector4f::zero(); + for (size_t j = 0; j < m_current_weights.size(); j++) { + v += (*m_cluts[j])[i].cast() * m_current_weights[j]; + } + m_temp_clut[i] = v.cast(); + } + + 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); + + return m_texture; +} + +TextureAnimator::TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level) + : m_common_level(common_level) { + glGenVertexArrays(1, &m_vao); + glGenBuffers(1, &m_vertex_buffer); + glBindVertexArray(m_vao); + + // The TextureAnimator does a lot of "draws" which are just a single quad, so we create a 4-vertex + // buffer. It turns out that just storing the vertex index in the vertex, then indexing into a + // uniform buffer is faster to update. (though this may be driver specific?) + std::array vertices = {Vertex{0, 0, 0, 0}, Vertex{1, 0, 0, 0}, Vertex{2, 0, 0, 0}, + Vertex{3, 0, 0, 0}}; + glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer); + // static draw - we don't update this buffer. + glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 4, vertices.data(), GL_STATIC_DRAW); + + // single integer index parameter + glEnableVertexAttribArray(0); + glVertexAttribIPointer(0, // location 0 in the shader + 1, // 1 per vertex + GL_INT, // + sizeof(Vertex), // + nullptr // + ); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + auto& shader = shaders[ShaderId::TEX_ANIM]; + m_shader_id = shader.id(); + m_uniforms.rgba = glGetUniformLocation(shader.id(), "rgba"); + m_uniforms.enable_tex = glGetUniformLocation(shader.id(), "enable_tex"); + m_uniforms.positions = glGetUniformLocation(shader.id(), "positions"); + m_uniforms.uvs = glGetUniformLocation(shader.id(), "uvs"); + m_uniforms.channel_scramble = glGetUniformLocation(shader.id(), "channel_scramble"); + m_uniforms.tcc = glGetUniformLocation(shader.id(), "tcc"); + + // 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); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + data.data()); + glBindTexture(GL_TEXTURE_2D, 0); + + shader.activate(); + + // generate CLUT table. + for (int i = 0; i < 256; i++) { + u32 clut_chunk = i / 16; + u32 off_in_chunk = i % 16; + u8 clx = 0, cly = 0; + if (clut_chunk & 1) { + clx = 8; + } + cly = (clut_chunk >> 1) * 2; + if (off_in_chunk >= 8) { + off_in_chunk -= 8; + cly++; + } + clx += off_in_chunk; + m_index_to_clut_addr[i] = clx + cly * 16; + } + + m_output_slots.resize(jak2_animated_texture_slots().size(), m_dummy_texture); + + // 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", {}); +} + +int TextureAnimator::create_clut_blender_group(const std::vector& textures, + const std::string& suffix0, + const std::string& suffix1, + const std::optional& dgo) { + int ret = m_clut_blender_groups.size(); + m_clut_blender_groups.emplace_back(); + add_to_clut_blender_group(ret, textures, suffix0, suffix1, dgo); + return ret; +} + +void TextureAnimator::add_to_clut_blender_group(int idx, + const std::vector& textures, + const std::string& suffix0, + const std::string& suffix1, + const std::optional& dgo) { + auto& grp = m_clut_blender_groups.at(idx); + for (auto& prefix : textures) { + 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(); + } +} + +TextureAnimator::~TextureAnimator() { + glDeleteVertexArrays(1, &m_vao); + glDeleteBuffers(1, &m_vertex_buffer); + glDeleteTextures(1, &m_dummy_texture); +} + +GLuint TextureAnimator::get_by_slot(int idx) { + ASSERT(idx >= 0 && idx < (int)m_output_slots.size()); + return m_output_slots[idx]; +} + +// IDs sent from GOAL telling us what texture operation to perform. +enum PcTextureAnimCodes { + FINISH_ARRAY = 13, + ERASE_DEST_TEXTURE = 14, + UPLOAD_CLUT_16_16 = 15, + GENERIC_UPLOAD = 16, + SET_SHADER = 17, + DRAW = 18, + MOVE_RG_TO_BA = 19, + SET_CLUT_ALPHA = 20, + COPY_CLUT_ALPHA = 21, + DARKJAK = 22, + PRISON_JAK = 23, + ORACLE_JAK = 24, + NEST_JAK = 25, + KOR_TRANSFORM = 26 +}; + +// metadata for an upload from GOAL memory +struct TextureAnimPcUpload { + u32 data; // goal pointer + u16 width; + u16 height; + u32 dest; // tbp address + // PS2 texture format of the _upload_ that was used. + // note that the data can be any format. They upload stuff in the wrong format sometimes, as + // an optimization (ps2 is fastest at psmct32) + u8 format; + u8 pad[3]; +}; +static_assert(sizeof(TextureAnimPcUpload) == 16); + +// metadata for an operation that operates on a source/destination texture. +struct TextureAnimPcTransform { + u32 src_tbp; + u32 dst_tbp; + u32 pad0; + u32 pad1; +}; + +/*! + * Main function to run texture animations from DMA. Updates textures in the pool. + */ +void TextureAnimator::handle_texture_anim_data(DmaFollower& dma, + const u8* ee_mem, + TexturePool* texture_pool) { + dprintf("animator\n"); + m_current_shader = {}; + glBindVertexArray(m_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer); + glUseProgram(m_shader_id); + glDepthMask(GL_FALSE); + for (auto& t : m_in_use_temp_textures) { + m_opengl_texture_pool.free(t.tex, t.w, t.h); + } + m_in_use_temp_textures.clear(); // reset temp texture allocator. + m_erased_on_this_frame.clear(); + + // loop over DMA, and do the appropriate texture operations. + // this will fill out m_textures, which is keyed on TBP. + // as much as possible, we keep around buffers/textures. + // this will also record which tbp's have been "erased", for the next step. + bool done = false; + while (!done) { + u32 offset = dma.current_tag_offset(); + auto tf = dma.read_and_advance(); + auto vif0 = tf.vifcode0(); + if (vif0.kind == VifCode::Kind::PC_PORT) { + switch (vif0.immediate) { + case UPLOAD_CLUT_16_16: { + auto p = scoped_prof("clut-16-16"); + handle_upload_clut_16_16(tf, ee_mem); + } break; + case ERASE_DEST_TEXTURE: { + auto p = scoped_prof("erase"); + handle_erase_dest(dma); + } break; + case GENERIC_UPLOAD: { + auto p = scoped_prof("generic-upload"); + handle_generic_upload(tf, ee_mem); + } break; + case SET_SHADER: { + auto p = scoped_prof("set-shader"); + handle_set_shader(dma); + } break; + case DRAW: { + auto p = scoped_prof("draw"); + handle_draw(dma, *texture_pool); + } break; + case FINISH_ARRAY: + done = true; + break; + case MOVE_RG_TO_BA: { + auto p = scoped_prof("rg-to-ba"); + handle_rg_to_ba(tf); + } break; + case SET_CLUT_ALPHA: { + auto p = scoped_prof("set-clut-alpha"); + handle_set_clut_alpha(tf); + } break; + case COPY_CLUT_ALPHA: { + auto p = scoped_prof("copy-clut-alpha"); + handle_copy_clut_alpha(tf); + } break; + case DARKJAK: { + auto p = scoped_prof("darkjak"); + run_clut_blender_group(tf, m_darkjak_clut_blender_idx); + } break; + case PRISON_JAK: { + auto p = scoped_prof("prisonjak"); + run_clut_blender_group(tf, m_jakb_prison_clut_blender_idx); + } break; + case ORACLE_JAK: { + auto p = scoped_prof("oraclejak"); + run_clut_blender_group(tf, m_jakb_oracle_clut_blender_idx); + } break; + case NEST_JAK: { + auto p = scoped_prof("nestjak"); + run_clut_blender_group(tf, m_jakb_nest_clut_blender_idx); + } break; + case KOR_TRANSFORM: { + auto p = scoped_prof("kor"); + run_clut_blender_group(tf, m_kor_transform_clut_blender_idx); + } break; + default: + fmt::print("bad imm: {}\n", vif0.immediate); + ASSERT_NOT_REACHED(); + } + } else { + printf("[tex anim] unhandled VIF in main loop\n"); + fmt::print("{} {}\n", vif0.print(), tf.vifcode1().print()); + fmt::print("dma address 0x{:x}\n", offset); + ASSERT_NOT_REACHED(); + } + } + + // The steps above will populate m_textures with some combination of GPU/CPU textures. + // we need to make sure that all final textures end up on the GPU. For now, we detect this by + // seeing if the "erase" operation ran on an tbp, indicating that it was cleared, which is + // always done to all textures by the GOAL code. + for (auto tbp : m_erased_on_this_frame) { + auto p = scoped_prof("handle-one-erased"); + force_to_gpu(tbp); + } + + // Loop over textures and put them in the pool if needed + for (auto& [tbp, entry] : m_textures) { + if (entry.kind != VramEntry::Kind::GPU) { + // not on the GPU, we can't put it in the texture pool. + // if it was skipped by the above step, this is just some temporary texture we don't need + // (hopefully) + // (TODO: could flag these somehow?) + continue; + } + dprintf("end processing on %d\n", tbp); + + // in the ideal case, the texture processing code will just modify the OpenGL texture in-place. + // however, if the size changes, or we need to add a new texture, we have additional work to + // do. + + if (entry.needs_pool_update) { + if (entry.pool_gpu_tex) { + // we have a GPU texture in the pool, but we need to change the actual texture. + auto p = scoped_prof("pool-update"); + ASSERT(entry.pool_gpu_tex); + // change OpenGL texture in the pool + texture_pool->update_gl_texture(entry.pool_gpu_tex, entry.tex_width, entry.tex_height, + entry.tex.value().texture()); + // set as the active texture in this vram slot (other textures can be loaded for + // different part of the frame that we need to replace). This is a fast operation. + texture_pool->move_existing_to_vram(entry.pool_gpu_tex, tbp); + entry.needs_pool_update = false; + dprintf("update texture %d\n", tbp); + } else { + // this is the first time we use a texture in this slot, so we need to create it. + // should happen only once per TBP. + auto p = scoped_prof("pool-create"); + TextureInput in; + in.gpu_texture = entry.tex.value().texture(); + in.w = entry.tex_width; + in.h = entry.tex_height; + in.debug_page_name = "PC-ANIM"; + in.debug_name = std::to_string(tbp); + in.id = get_id_for_tbp(texture_pool, tbp); + entry.pool_gpu_tex = texture_pool->give_texture_and_load_to_vram(in, tbp); + entry.needs_pool_update = false; + dprintf("create texture %d\n", tbp); + } + } else { + // ideal case: OpenGL texture modified in place, just have to simulate "upload". + auto p = scoped_prof("pool-move"); + texture_pool->move_existing_to_vram(entry.pool_gpu_tex, tbp); + dprintf("no change %d\n", tbp); + } + } + + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glColorMask(true, true, true, true); +} + +/*! + * Make sure that this texture is a GPU texture. If not, convert it. + * GPU textures don't support CLUT, so this should be done at the last possible point in time, as + * CLUT effects can no longer be applied to the texture after this happens. + */ +void TextureAnimator::force_to_gpu(int tbp) { + auto& entry = m_textures.at(tbp); + switch (entry.kind) { + default: + printf("unhandled non-gpu conversion: %d (tbp = %d)\n", (int)entry.kind, tbp); + ASSERT_NOT_REACHED(); + case VramEntry::Kind::CLUT16_16_IN_PSM32: + // HACK: never convert known CLUT textures to GPU. + // The main loop will incorrectly flag CLUT textures as final ones because we can't tell + // the difference. So hopefully this is just an optimization. But we'll have to revisit if + // they use texture data as both texture/clut. + dprintf("suspicious clut...\n"); + break; + case VramEntry::Kind::GPU: + break; // already on the gpu. + case VramEntry::Kind::GENERIC_PSMT8: { + // we have data that was uploaded in PSMT8 format. Assume that it will also be read in this + // format. Convert to normal format. + int tw = entry.tex_width; + int th = entry.tex_height; + std::vector rgba_data(tw * th); + + { + auto p = scoped_prof("convert"); + // the CLUT is usually uploaded in PSM32 format, as a 16x16. + const u32* clut = get_clut_16_16_psm32(entry.cbp); + for (int r = 0; r < th; r++) { + for (int c = 0; c < tw; c++) { + rgba_data[c + r * tw] = clut[m_index_to_clut_addr[entry.data[c + r * tw]]]; + } + } + } + + // do OpenGL tricks to make sure this entry is set up to hold a texture with the size. + // will also set flags for updating the pool + setup_vram_entry_for_gpu_texture(tw, th, tbp); + // load the texture. + glBindTexture(GL_TEXTURE_2D, entry.tex.value().texture()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + rgba_data.data()); + glBindTexture(GL_TEXTURE_2D, 0); + entry.kind = VramEntry::Kind::GPU; + } break; + } +} + +/*! + * Get a pool texture ID for this texture. For now, there's just a unique ID per TBP. + * The only purpose is to avoid putting all the textures with the same ID, which is a slow-path + * in the pool (which is optimized for only a few textures with the same ID at most). + */ +PcTextureId TextureAnimator::get_id_for_tbp(TexturePool* pool, u32 tbp) { + const auto& it = m_ids_by_vram.find(tbp); + if (it == m_ids_by_vram.end()) { + auto ret = pool->allocate_pc_port_texture(GameVersion::Jak2); + m_ids_by_vram[tbp] = ret; + return ret; + } else { + return it->second; + } +} + +void debug_save_opengl_texture(const std::string& out, GLuint texture) { + glBindTexture(GL_TEXTURE_2D, texture); + int w, h; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); + fmt::print("saving texture with size {} x {}\n", w, h); + std::vector data(w * h * 4); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data.data()); + file_util::write_rgba_png(out, data.data(), w, h); +} + +/*! + * Copy rg channels to ba from src to dst. + * The PS2 implementation is confusing, and this is just a guess at how it works. + */ +void TextureAnimator::handle_rg_to_ba(const DmaTransfer& tf) { + dprintf("[tex anim] rg -> ba\n"); + ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform)); + auto* data = (const TextureAnimPcTransform*)(tf.data); + dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp); + const auto& src = m_textures.find(data->src_tbp); + const auto& dst = m_textures.find(data->dst_tbp); + if (src != m_textures.end() && dst != m_textures.end()) { + ASSERT(src->second.kind == VramEntry::Kind::GPU); + ASSERT(dst->second.kind == VramEntry::Kind::GPU); + ASSERT(src->second.tex.value().texture() != dst->second.tex.value().texture()); + FramebufferTexturePairContext ctxt(dst->second.tex.value()); + float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1}; + glUniform3fv(m_uniforms.positions, 4, positions); + glUniform2fv(m_uniforms.uvs, 4, uvs); + 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); + 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); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glColorMask(true, true, true, true); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + } else { + ASSERT_NOT_REACHED(); + } + + // const auto& vram_src = m_vram_entries.find(data->src_tbp); + // if (vram_src != m_vram_entries.end()) { + // ASSERT(vram_src->second.kind == VramEntry::Kind::GENERIC_PSM32); + // // no idea if this is right, but lets try. + // int w = vram_src->second.width; + // int h = vram_src->second.height; + // u8* tdata = vram_src->second.data_psm32.data(); + // + // // file_util::write_rgba_png("./before_transform.png", tdata, w, h); + // + // for (int i = 0; i < w * h; i++) { + // tdata[i * 4 + 2] = tdata[i * 4]; + // tdata[i * 4 + 3] = tdata[i * 4 + 1]; + // } + // + // // file_util::write_rgba_png("./after_transform.png", tdata, w, h); + // + // } else { + // ASSERT_NOT_REACHED(); + // } +} + +void TextureAnimator::handle_set_clut_alpha(const DmaTransfer& tf) { + ASSERT_NOT_REACHED(); + dprintf("[tex anim] set clut alpha\n"); + ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform)); + auto* data = (const TextureAnimPcTransform*)(tf.data); + dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp); + const auto& tex = m_textures.find(data->dst_tbp); + ASSERT(tex != m_textures.end()); + + ASSERT(tex->second.kind == VramEntry::Kind::GPU); + FramebufferTexturePairContext ctxt(tex->second.tex.value()); + float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1}; + glUniform3fv(m_uniforms.positions, 4, positions); + glUniform2fv(m_uniforms.uvs, 4, uvs); + glUniform1i(m_uniforms.enable_tex, 0); // NO TEXTURE! + glUniform4f(m_uniforms.rgba, 128, 128, 128, 128); + 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(false, false, false, true); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glColorMask(true, true, true, true); +} + +void TextureAnimator::handle_copy_clut_alpha(const DmaTransfer& tf) { + ASSERT_NOT_REACHED(); + dprintf("[tex anim] __copy__ clut alpha\n"); + ASSERT(tf.size_bytes == sizeof(TextureAnimPcTransform)); + auto* data = (const TextureAnimPcTransform*)(tf.data); + dprintf(" src: %d, dest: %d\n", data->src_tbp, data->dst_tbp); + const auto& dst_tex = m_textures.find(data->dst_tbp); + const auto& src_tex = m_textures.find(data->src_tbp); + ASSERT(dst_tex != m_textures.end()); + if (src_tex == m_textures.end()) { + lg::error("Skipping copy clut alpha because source texture at {} wasn't found", data->src_tbp); + return; + } + ASSERT(src_tex != m_textures.end()); + + ASSERT(dst_tex->second.kind == VramEntry::Kind::GPU); + ASSERT(src_tex->second.kind == VramEntry::Kind::GPU); + + FramebufferTexturePairContext ctxt(dst_tex->second.tex.value()); + float positions[3 * 4] = {0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0}; + float uvs[2 * 4] = {0, 0, 1, 0, 1, 1, 0, 1}; + glUniform3fv(m_uniforms.positions, 4, positions); + glUniform2fv(m_uniforms.uvs, 4, uvs); + glUniform1i(m_uniforms.enable_tex, 1); + glUniform4f(m_uniforms.rgba, 128, 128, 128, 128); // TODO - seems wrong. + glUniform4i(m_uniforms.channel_scramble, 0, 1, 2, 3); + glBindTexture(GL_TEXTURE_2D, src_tex->second.tex.value().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(false, false, false, true); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glColorMask(true, true, true, true); +} + +void TextureAnimator::run_clut_blender_group(DmaTransfer& tf, int idx) { + float f; + ASSERT(tf.size_bytes == 16); + memcpy(&f, tf.data, sizeof(float)); + 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); + } +} + +/*! + * Create an entry for a 16x16 clut texture upload. Leaves it on the CPU. + * They upload cluts as PSM32, so there's no funny addressing stuff, other than + * the CLUT indexing scramble stuff. + */ +void TextureAnimator::handle_upload_clut_16_16(const DmaTransfer& tf, const u8* ee_mem) { + dprintf("[tex anim] upload clut 16 16\n"); + ASSERT(tf.size_bytes == sizeof(TextureAnimPcUpload)); + auto* upload = (const TextureAnimPcUpload*)(tf.data); + ASSERT(upload->width == 16); + ASSERT(upload->height == 16); + dprintf(" dest is 0x%x\n", upload->dest); + auto& vram = m_textures[upload->dest]; + vram.reset(); + vram.kind = VramEntry::Kind::CLUT16_16_IN_PSM32; + vram.data.resize(16 * 16 * 4); + vram.tex_width = upload->width; + vram.tex_height = upload->height; + memcpy(vram.data.data(), ee_mem + upload->data, vram.data.size()); + if (m_tex_looking_for_clut) { + m_tex_looking_for_clut->cbp = upload->dest; + m_tex_looking_for_clut = nullptr; + } +} + +/*! + * Create an entry for any texture upload. Leaves it on the CPU, as we may do fancy scramble stuff. + */ +void TextureAnimator::handle_generic_upload(const DmaTransfer& tf, const u8* ee_mem) { + dprintf("[tex anim] upload generic @ 0x%lx\n", tf.data - ee_mem); + ASSERT(tf.size_bytes == sizeof(TextureAnimPcUpload)); + auto* upload = (const TextureAnimPcUpload*)(tf.data); + dprintf(" size %d x %d\n", upload->width, upload->height); + dprintf(" dest is 0x%x\n", upload->dest); + auto& vram = m_textures[upload->dest]; + vram.reset(); + + switch (upload->format) { + case (int)GsTex0::PSM::PSMCT32: + vram.kind = VramEntry::Kind::GENERIC_PSM32; + vram.data.resize(upload->width * upload->height * 4); + vram.tex_width = upload->width; + vram.tex_height = upload->height; + memcpy(vram.data.data(), ee_mem + upload->data, vram.data.size()); + m_tex_looking_for_clut = nullptr; + break; + case (int)GsTex0::PSM::PSMT8: + vram.kind = VramEntry::Kind::GENERIC_PSMT8; + vram.data.resize(upload->width * upload->height); + vram.tex_width = upload->width; + vram.tex_height = upload->height; + memcpy(vram.data.data(), ee_mem + upload->data, vram.data.size()); + m_tex_looking_for_clut = &vram; + break; + default: + fmt::print("Unhandled format: {}\n", upload->format); + ASSERT_NOT_REACHED(); + } +} + +/*! + * Handle the initialization of an animated texture. This fills the entire texture with a solid + * color. We set up a GPU texture here - drawing operations are done on the GPU, so we'd never + * need this solid color on the CPU. Also sets a bunch of GS state for the shaders. + * These may be modified by animation functions, but most of the time they aren't. + */ +void TextureAnimator::handle_erase_dest(DmaFollower& dma) { + dprintf("[tex anim] erase destination texture\n"); + // auto& out = m_new_dest_textures.emplace_back(); + VramEntry* entry = nullptr; + + // first transfer will be a bunch of ad (modifies the shader) + { + auto ad_transfer = dma.read_and_advance(); + ASSERT(ad_transfer.size_bytes == 10 * 16); + ASSERT(ad_transfer.vifcode0().kind == VifCode::Kind::FLUSHA); + ASSERT(ad_transfer.vifcode1().kind == VifCode::Kind::DIRECT); + const u64* ad_data = (const u64*)(ad_transfer.data + 16); + + // for (int i = 0; i < 9; i++) { + // dprintf(" ad: 0x%lx 0x%lx\n", ad_data[i * 2], ad_data[i * 2 + 1]); + // } + // 0 (scissor-1 (new 'static 'gs-scissor :scax1 (+ tex-width -1) :scay1 (+ tex-height -1))) + ASSERT(ad_data[0 * 2 + 1] == (int)GsRegisterAddress::SCISSOR_1); + GsScissor scissor(ad_data[0]); + int tex_width = scissor.x1() + 1; + int tex_height = scissor.y1() + 1; + dprintf(" size: %d x %d\n", tex_width, tex_height); + + // 1 (xyoffset-1 (new 'static 'gs-xy-offset :ofx #x8000 :ofy #x8000)) + // 2 (frame-1 (new 'static 'gs-frame :fbw (/ (+ tex-width 63) 64) :fbp fbp-for-tex)) + ASSERT(ad_data[2 * 2 + 1] == (int)GsRegisterAddress::FRAME_1); + GsFrame frame(ad_data[2 * 2]); + int dest_texture_address = 32 * frame.fbp(); + dprintf(" dest: 0x%x\n", dest_texture_address); + + // 3 (test-1 (-> anim test)) + ASSERT(ad_data[2 * 3 + 1] == (int)GsRegisterAddress::TEST_1); + m_current_shader.test = GsTest(ad_data[3 * 2]); + dfmt(" test: {}", m_current_shader.test.print()); + + // 4 (alpha-1 (-> anim alpha)) + ASSERT(ad_data[2 * 4 + 1] == (int)GsRegisterAddress::ALPHA_1); + m_current_shader.alpha = GsAlpha(ad_data[4 * 2]); + dfmt(" alpha: {}\n", m_current_shader.alpha.print()); + + // 5 (clamp-1 (-> anim clamp)) + ASSERT(ad_data[2 * 5 + 1] == (int)GsRegisterAddress::CLAMP_1); + u64 creg = ad_data[5 * 2]; + m_current_shader.clamp_u = creg & 0b001; + m_current_shader.clamp_v = creg & 0b100; + u64 mask = ~0b101; + ASSERT((creg & mask) == 0); + dfmt(" clamp: {} {}\n", m_current_shader.clamp_u, m_current_shader.clamp_v); + + // 6 (texa (new 'static 'gs-texa :ta0 #x80 :ta1 #x80)) + // 7 (zbuf-1 (new 'static 'gs-zbuf :zbp #x130 :psm (gs-psm ct24) :zmsk #x1)) + // 8 (texflush 0) + + // get the entry set up for being a GPU texture. + entry = setup_vram_entry_for_gpu_texture(tex_width, tex_height, dest_texture_address); + } + + // next transfer is the erase. This is done with alpha blending off + auto clear_transfer = dma.read_and_advance(); + ASSERT(clear_transfer.size_bytes == 16 * 4); + math::Vector rgba_u32; + memcpy(rgba_u32.data(), clear_transfer.data + 16, 16); + dfmt(" clear: {}\n", rgba_u32.to_string_hex_byte()); + + // create the opengl output texture. + + // do the clear: + { + FramebufferTexturePairContext ctxt(entry->tex.value()); + 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, rgba_u32[0], rgba_u32[1], rgba_u32[2], rgba_u32[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); + { + auto p = scoped_prof("erase-draw"); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + } + + // set as active + m_current_dest_tbp = entry->dest_texture_address; + m_erased_on_this_frame.insert(entry->dest_texture_address); +} + +/*! + * Set up this texture as a GPU texture. This does a few things: + * - sets the Kind to GPU + * - makes sure the texture resource points to a valid OpenGL texture of the right size, without + * triggering the resize/delete sync issue mentioned above. + * - sets flags to indicate if this GPU texture needs to be updated in the pool. + */ +VramEntry* TextureAnimator::setup_vram_entry_for_gpu_texture(int w, int h, int tbp) { + auto pp = scoped_prof("setup-vram-entry"); + const auto& existing_dest = m_textures.find(tbp); + + // see if we have an existing OpenGL texture at all + bool existing_opengl = existing_dest != m_textures.end() && existing_dest->second.tex.has_value(); + + // see if we can reuse it (same size) + bool can_reuse = true; + if (existing_opengl) { + if (existing_dest->second.tex->height() != h || existing_dest->second.tex->width() != w) { + dprintf(" can't reuse, size mismatch\n"); + can_reuse = false; + } + } else { + dprintf(" can't reuse, first time using this address\n"); + can_reuse = false; + } + + VramEntry* entry = nullptr; + if (can_reuse) { + // texture is the right size, just use it again. + entry = &existing_dest->second; + } else { + if (existing_opengl) { + // we have a texture, but it's the wrong type. Remember that we need to update the pool + entry = &existing_dest->second; + entry->needs_pool_update = true; + } else { + // create the entry. Also need to update the pool + entry = &m_textures[tbp]; + entry->reset(); + entry->needs_pool_update = true; + } + + // if we already have a texture, try to swap it with an OpenGL texture of the right size. + if (entry->tex.has_value()) { + // gross + m_opengl_texture_pool.free(entry->tex->texture(), entry->tex->width(), entry->tex->height()); + entry->tex->update_texture_size(w, h); + entry->tex->update_texture_unsafe(m_opengl_texture_pool.allocate(w, h)); + } else { + entry->tex.emplace(w, h, GL_UNSIGNED_INT_8_8_8_8_REV); + } + } + + entry->kind = VramEntry::Kind::GPU; + entry->tex_width = w; + entry->tex_height = h; + entry->dest_texture_address = tbp; + return entry; +} + +/*! + * ADGIF shader update + */ +void TextureAnimator::handle_set_shader(DmaFollower& dma) { + dprintf("[tex anim] set shader\n"); + auto ad_transfer = dma.read_and_advance(); + const int num_regs = (ad_transfer.size_bytes - 16) / 16; + ASSERT(ad_transfer.vifcode0().kind == VifCode::Kind::NOP || + ad_transfer.vifcode0().kind == VifCode::Kind::FLUSHA); + ASSERT(ad_transfer.vifcode1().kind == VifCode::Kind::DIRECT); + const u64* ad_data = (const u64*)(ad_transfer.data + 16); + + for (int i = 0; i < num_regs; i++) { + u64 addr = ad_data[i * 2 + 1]; + u64 data = ad_data[i * 2]; + + switch (GsRegisterAddress(addr)) { + case GsRegisterAddress::TEX0_1: + m_current_shader.tex0 = GsTex0(data); + m_current_shader.source_texture_set = true; + dfmt(" tex0: {}", m_current_shader.tex0.print()); + break; + case GsRegisterAddress::TEX1_1: + m_current_shader.tex1 = GsTex1(data); + dfmt(" tex1: {}", m_current_shader.tex1.print()); + break; + case GsRegisterAddress::TEST_1: + m_current_shader.test = GsTest(data); + dfmt(" test: {}", m_current_shader.test.print()); + break; + case GsRegisterAddress::ALPHA_1: + m_current_shader.alpha = GsAlpha(data); + dfmt(" alpha: {}\n", m_current_shader.alpha.print()); + break; + case GsRegisterAddress::CLAMP_1: + m_current_shader.clamp_u = data & 0b001; + m_current_shader.clamp_v = data & 0b100; + ASSERT((data & (~(u64(0b101)))) == 0); + dfmt(" clamp: {} {}\n", m_current_shader.clamp_u, m_current_shader.clamp_v); + break; + default: + dfmt("unknown reg {}\n", addr); + ASSERT_NOT_REACHED(); + } + } +} + +/*! + * Do a draw to a destination texture. + */ +void TextureAnimator::handle_draw(DmaFollower& dma, TexturePool& texture_pool) { + // NOTE: assuming ABE set from the template here. If this function is used for other templates, + // we'll need to actually check. + dprintf("[tex anim] Draw\n"); + DrawData draw_data; + auto draw_xfer = dma.read_and_advance(); + ASSERT(draw_xfer.size_bytes == sizeof(DrawData)); + memcpy(&draw_data, draw_xfer.data, sizeof(DrawData)); + + if (m_current_shader.source_texture_set) { + // find the destination we draw to. It should have been erased previously, making it a GPU + // texture + auto& dest_te = m_textures.at(m_current_dest_tbp); + ASSERT(dest_te.kind == VramEntry::Kind::GPU); + + // set up context to draw to this one + FramebufferTexturePairContext ctxt(*dest_te.tex); + + // get the source texture + GLuint gpu_texture; + { + auto p = scoped_prof("make-tex"); + gpu_texture = make_or_get_gpu_texture_for_current_shader(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. + + // 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); + // debug_save_opengl_texture("opengl_draw_result.png", dest_te.tex->texture()); + // debug_save_opengl_texture("opengl_test.png", gpu_texture); + } else { + ASSERT_NOT_REACHED(); + } +} + +/*! + * Get a 16x16 CLUT texture, stored in psm32 (in-memory format, not vram). Fatal if it doesn't + * exist. + */ +const u32* TextureAnimator::get_clut_16_16_psm32(int cbp) { + const auto& clut_lookup = m_textures.find(cbp); + if (clut_lookup == m_textures.end()) { + printf("get_clut_16_16_psm32 referenced an unknown clut texture in %d\n", cbp); + ASSERT_NOT_REACHED(); + } + + if (clut_lookup->second.kind != VramEntry::Kind::CLUT16_16_IN_PSM32) { + ASSERT_NOT_REACHED(); + } + + return (const u32*)clut_lookup->second.data.data(); +} + +/*! + * Using the current shader settings, load the CLUT table to the texture coverter "VRAM". + */ +void TextureAnimator::load_clut_to_converter() { + const auto& clut_lookup = m_textures.find(m_current_shader.tex0.cbp()); + if (clut_lookup == m_textures.end()) { + printf("set shader referenced an unknown clut texture in %d\n", m_current_shader.tex0.cbp()); + ASSERT_NOT_REACHED(); + } + + switch (clut_lookup->second.kind) { + case VramEntry::Kind::CLUT16_16_IN_PSM32: + m_converter.upload_width(clut_lookup->second.data.data(), m_current_shader.tex0.cbp(), 16, + 16); + break; + default: + printf("unhandled clut source kind: %d\n", (int)clut_lookup->second.kind); + ASSERT_NOT_REACHED(); + } +} + +GLuint TextureAnimator::make_temp_gpu_texture(const u32* data, u32 width, u32 height) { + GLuint gl_tex = m_opengl_texture_pool.allocate(width, height); + m_in_use_temp_textures.push_back(TempTexture{gl_tex, width, height}); + glBindTexture(GL_TEXTURE_2D, gl_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + data); + glBindTexture(GL_TEXTURE_2D, 0); + return gl_tex; +} + +/*! + * Read the current shader settings, and get/create/setup a GPU texture for the source texture. + */ +GLuint TextureAnimator::make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool) { + u32 tbp = m_current_shader.tex0.tbp0(); + const auto& lookup = m_textures.find(m_current_shader.tex0.tbp0()); + if (lookup == m_textures.end()) { + auto tpool = texture_pool.lookup(tbp); + if (tpool.has_value()) { + return *tpool; + } + // printf("referenced an unknown texture in %d\n", tbp); + lg::error("unknown texture in {} (0x{:x})", tbp, tbp); + return texture_pool.get_placeholder_texture(); + + // ASSERT_NOT_REACHED(); + } + + auto* vram_entry = &lookup->second; + + // see what format the source is + switch (vram_entry->kind) { + case VramEntry::Kind::GPU: + // already on the GPU, just return it. + return lookup->second.tex->texture(); + // data on the CPU, in PSM32 + case VramEntry::Kind::GENERIC_PSM32: + // see how we're reading it: + switch (m_current_shader.tex0.psm()) { + // reading as a different format, needs scrambler. + case GsTex0::PSM::PSMT8: { + int w = 1 << m_current_shader.tex0.tw(); + int h = 1 << m_current_shader.tex0.th(); + ASSERT(w == vram_entry->tex_width * 2); + ASSERT(h == vram_entry->tex_height * 2); + + Timer timer; + m_converter.upload_width(vram_entry->data.data(), m_current_shader.tex0.tbp0(), + vram_entry->tex_width, vram_entry->tex_height); + + // also needs clut lookup + load_clut_to_converter(); + { + std::vector rgba_data(w * h); + m_converter.download_rgba8888( + (u8*)rgba_data.data(), m_current_shader.tex0.tbp0(), m_current_shader.tex0.tbw(), w, + h, (int)m_current_shader.tex0.psm(), (int)m_current_shader.tex0.cpsm(), + m_current_shader.tex0.cbp(), rgba_data.size() * 4); + // file_util::write_rgba_png("out.png", rgba_data.data(), 1 << + // m_current_shader.tex0.tw(), + // 1 << m_current_shader.tex0.th()); + dprintf("processing %d x %d took %.3f ms\n", w, h, timer.getMs()); + return make_temp_gpu_texture(rgba_data.data(), w, h); + } + + ASSERT_NOT_REACHED(); + } break; + default: + fmt::print("unhandled source texture format {}\n", (int)m_current_shader.tex0.psm()); + ASSERT_NOT_REACHED(); + } + break; + case VramEntry::Kind::CLUT16_16_IN_PSM32: + ASSERT_NOT_REACHED(); + + /* + case VramEntry::Kind::GENERIC_PSMT8: { + fmt::print("drawing: {}\n", (int)m_current_shader.tex0.psm()); + ASSERT(m_current_shader.tex0.psm() == GsTex0::PSM::PSMT8); + ASSERT(m_current_shader.tex0.cpsm() == 0); // psm32. + int tw = 1 << m_current_shader.tex0.tw(); + int th = 1 << m_current_shader.tex0.th(); + ASSERT(tw == vram_entry->tex_width); + ASSERT(th == vram_entry->tex_height); + std::vector rgba_data(tw * th); + const u32* clut = get_current_clut_16_16_psm32(); + for (int r = 0; r < th; r++) { + for (int c = 0; c < tw; c++) { + rgba_data[c + r * tw] = clut[vram_entry->data[c + r * tw]]; + } + } + return make_temp_gpu_texture(rgba_data.data(), tw, th); + } + */ + + break; + default: + ASSERT_NOT_REACHED(); + } +} + +void TextureAnimator::set_up_opengl_for_shader(const ShaderContext& shader, + std::optional texture, + bool prim_abe) { + 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 + u32 tcc = shader.tex0.tcc(); + ASSERT(tcc == 1 || tcc == 0); + glUniform1i(m_uniforms.tcc, tcc); + + ASSERT(shader.tex0.tfx() == GsTex0::TextureFunction::MODULATE); + // tex1 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + shader.tex1.mmag() ? GL_LINEAR : GL_NEAREST); + switch (shader.tex1.mmin()) { + case 0: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + break; + case 1: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + break; + default: + ASSERT_NOT_REACHED(); + } + + bool do_alpha_test = false; + bool alpha_test_mask_alpha_trick = false; + bool alpha_test_mask_depth_trick = false; + + // test + if (shader.test.alpha_test_enable()) { + auto atst = shader.test.alpha_test(); + if (atst == GsTest::AlphaTest::ALWAYS) { + do_alpha_test = false; + // atest effectively disabled - everybody passes. + } else if (atst == GsTest::AlphaTest::NEVER) { + // everybody fails. They use alpha test to mask out some channel + do_alpha_test = false; + + switch (shader.test.afail()) { + case GsTest::AlphaFail::RGB_ONLY: + alpha_test_mask_alpha_trick = true; + break; + case GsTest::AlphaFail::FB_ONLY: + alpha_test_mask_depth_trick = true; + break; + default: + ASSERT_NOT_REACHED(); + } + + } else { + ASSERT_NOT_REACHED(); + } + } else { + do_alpha_test = false; + } + + if (alpha_test_mask_alpha_trick) { + glColorMask(true, true, true, false); + } else { + glColorMask(true, true, true, true); + } + + if (alpha_test_mask_depth_trick) { + glDepthMask(GL_FALSE); + } else { + glDepthMask(GL_TRUE); + } + + ASSERT(shader.test.date() == false); + // DATM + ASSERT(shader.test.zte() == true); // required + switch (shader.test.ztest()) { + case GsTest::ZTest::ALWAYS: + glDisable(GL_DEPTH_TEST); + break; + default: + ASSERT(false); + } + + if (shader.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 (shader.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 (prim_abe) { + auto blend_a = shader.alpha.a_mode(); + auto blend_b = shader.alpha.b_mode(); + auto blend_c = shader.alpha.c_mode(); + auto blend_d = shader.alpha.d_mode(); + 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); +} + +namespace { +void set_uniform(GLuint uniform, const math::Vector& vf) { + glUniform4f(uniform, vf.x(), vf.y(), vf.z(), vf.w()); +} + +void convert_gs_position_to_vec3(float* out, const math::Vector& in, int w, int h) { + out[0] = ((((float)in.x()) / 16.f) - 2048.f) / (float)w; + out[1] = ((((float)in.y()) / 16.f) - 2048.f) / (float)h; + out[2] = 0; // in.z(); // don't think it matters +} + +void convert_gs_uv_to_vec2(float* out, const math::Vector& in) { + out[0] = in.x(); + out[1] = in.y(); +} +} // namespace + +void TextureAnimator::set_uniforms_from_draw_data(const DrawData& dd, int dest_w, int dest_h) { + set_uniform(m_uniforms.rgba, dd.color.cast()); + + float pos[3 * 4 + 1]; + convert_gs_position_to_vec3(pos, dd.pos0, dest_w, dest_h); + convert_gs_position_to_vec3(pos + 3, dd.pos1, dest_w, dest_h); + convert_gs_position_to_vec3(pos + 6, dd.pos2, dest_w, dest_h); + convert_gs_position_to_vec3(pos + 9, dd.pos3, dest_w, dest_h); + glUniform3fv(m_uniforms.positions, 4, pos); + // for (int i = 0; i < 4; i++) { + // fmt::print("fan vp {}: {:.3f} {:.3f} {:.3f}\n", i, pos[i * 3], pos[1 + i * 3], pos[2 + i * + // 3]); + // } + + float uv[2 * 4]; + convert_gs_uv_to_vec2(uv, dd.st0); + convert_gs_uv_to_vec2(uv + 2, dd.st1); + convert_gs_uv_to_vec2(uv + 4, dd.st2); + convert_gs_uv_to_vec2(uv + 6, dd.st3); + glUniform2fv(m_uniforms.uvs, 4, uv); + // for (int i = 0; i < 4; i++) { + // fmt::print("fan vt {}: {:.3f} {:.3f} \n", i, uv[i * 2], uv[1 + i * 2]); + // } +} diff --git a/game/graphics/opengl_renderer/TextureAnimator.h b/game/graphics/opengl_renderer/TextureAnimator.h index 30c29449a..50b21a48d 100644 --- a/game/graphics/opengl_renderer/TextureAnimator.h +++ b/game/graphics/opengl_renderer/TextureAnimator.h @@ -1,8 +1,215 @@ #pragma once +#include +#include +#include +#include + +#include "common/custom_data/Tfrag3Data.h" #include "common/dma/dma_chain_read.h" +#include "common/dma/gs.h" +#include "common/math/Vector.h" + +#include "game/graphics/opengl_renderer/Shader.h" +#include "game/graphics/opengl_renderer/opengl_utils.h" +#include "game/graphics/pipelines/opengl.h" +#include "game/graphics/texture/TextureConverter.h" +#include "game/graphics/texture/TextureID.h" + +struct GpuTexture; + +struct VramEntry { + enum class Kind { CLUT16_16_IN_PSM32, GENERIC_PSM32, GENERIC_PSMT8, GPU, INVALID } kind; + std::vector data; + + int tex_width = 0; + int tex_height = 0; + int dest_texture_address = 0; + int cbp = 0; + std::optional tex; + // math::Vector rgba_clear; + + bool needs_pool_update = false; + GpuTexture* pool_gpu_tex = nullptr; + + void reset() { + data.clear(); + kind = Kind::INVALID; + tex_height = 0; + tex_width = 0; + cbp = 0; + // tex.reset(); + needs_pool_update = false; + // pool_gpu_tex = nullptr; + } +}; + +struct ShaderContext { + GsTex0 tex0; + GsTex1 tex1; + GsTest test; + bool clamp_u, clamp_v; + GsAlpha alpha; + bool source_texture_set = false; +}; + +struct OpenGLTexturePool { + OpenGLTexturePool(); + ~OpenGLTexturePool(); + GLuint allocate(u64 w, u64 h); + void free(GLuint texture, u64 w, u64 h); + std::unordered_map> textures; +}; + +class ClutBlender { + public: + ClutBlender(const std::string& dest, + const std::vector& sources, + const std::optional& level_name, + const tfrag3::Level* level, + OpenGLTexturePool* tpool); + GLuint run(const float* weights); + GLuint texture() const { return m_texture; } + + private: + const tfrag3::IndexTexture* m_dest; + std::vector, 256>*> m_cluts; + std::vector m_current_weights; + GLuint m_texture; + std::array, 256> m_temp_clut; + std::vector m_temp_rgba; +}; + +class TexturePool; class TextureAnimator { public: - void handle_texture_anim_data(DmaFollower& dma); + TextureAnimator(ShaderLibrary& shaders, const tfrag3::Level* common_level); + ~TextureAnimator(); + void handle_texture_anim_data(DmaFollower& dma, const u8* ee_mem, TexturePool* texture_pool); + GLuint get_by_slot(int idx); + const std::vector* slots() { return &m_output_slots; } + + private: + 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); + void handle_set_shader(DmaFollower& dma); + void handle_draw(DmaFollower& dma, TexturePool& texture_pool); + void handle_rg_to_ba(const DmaTransfer& tf); + void handle_set_clut_alpha(const DmaTransfer& tf); + void handle_copy_clut_alpha(const DmaTransfer& tf); + + VramEntry* setup_vram_entry_for_gpu_texture(int w, int h, int tbp); + + void set_up_opengl_for_shader(const ShaderContext& shader, + std::optional texture, + bool prim_abe); + + void load_clut_to_converter(); + const u32* get_clut_16_16_psm32(int cbp); + + GLuint make_temp_gpu_texture(const u32* data, u32 width, u32 height); + + GLuint make_or_get_gpu_texture_for_current_shader(TexturePool& texture_pool); + void force_to_gpu(int tbp); + + struct DrawData { + u8 tmpl1[16]; + math::Vector color; + + math::Vector st0; + math::Vector pos0; + + math::Vector st1; + math::Vector pos1; + + math::Vector st2; + math::Vector pos2; + + math::Vector st3; + math::Vector pos3; + }; + + void set_uniforms_from_draw_data(const DrawData& dd, int dest_w, int dest_h); + + PcTextureId get_id_for_tbp(TexturePool* pool, u32 tbp); + + VramEntry* m_tex_looking_for_clut = nullptr; + const tfrag3::Level* m_common_level = nullptr; + std::unordered_map m_textures; + std::unordered_map m_ids_by_vram; + + std::set m_erased_on_this_frame; + + struct TempTexture { + GLuint tex; + u32 w, h; + }; + std::vector m_in_use_temp_textures; + + ShaderContext m_current_shader; + TextureConverter m_converter; + int m_current_dest_tbp = -1; + + GLuint m_vao; + GLuint m_vertex_buffer; + struct Vertex { + u32 index; + u32 pad1; + u32 pad2; + u32 pad3; + }; + + struct { + GLuint rgba; + GLuint positions; + GLuint uvs; + GLuint enable_tex; + GLuint channel_scramble; + GLuint tcc; + } m_uniforms; + + GLuint m_shader_id; + GLuint m_dummy_texture; + + u8 m_index_to_clut_addr[256]; + OpenGLTexturePool m_opengl_texture_pool; + + std::vector m_output_slots; + + struct ClutBlenderGroup { + std::vector blenders; + std::vector outputs; + }; + std::vector m_clut_blender_groups; + + int m_darkjak_clut_blender_idx = -1; + int m_jakb_prison_clut_blender_idx = -1; + int m_jakb_oracle_clut_blender_idx = -1; + int m_jakb_nest_clut_blender_idx = -1; + int m_kor_transform_clut_blender_idx = -1; + + int create_clut_blender_group(const std::vector& textures, + const std::string& suffix0, + const std::string& suffix1, + const std::optional& dgo); + void add_to_clut_blender_group(int idx, + const std::vector& textures, + const std::string& suffix0, + const std::string& suffix1, + const std::optional& dgo); + void run_clut_blender_group(DmaTransfer& tf, int idx); + + // std::vector m_darkjak_blenders; + // std::vector m_darkjak_output_slots; + // + // std::vector m_jakb_prison_blenders; + // std::vector m_jakb_prison_output_slots; + // + // std::vector m_jakb_oracle_blenders; + // std::vector m_jakb_oracle_slots; + // + // std::vector m_jakb_nest_blenders; + // std::vector m_jakb_nest_slots; }; diff --git a/game/graphics/opengl_renderer/TextureUploadHandler.cpp b/game/graphics/opengl_renderer/TextureUploadHandler.cpp index cda48dae7..5b9cee142 100644 --- a/game/graphics/opengl_renderer/TextureUploadHandler.cpp +++ b/game/graphics/opengl_renderer/TextureUploadHandler.cpp @@ -1,5 +1,6 @@ #include "TextureUploadHandler.h" +#include "common/global_profiler/GlobalProfiler.h" #include "common/log/log.h" #include "game/graphics/opengl_renderer/EyeRenderer.h" @@ -8,8 +9,10 @@ #include "third-party/fmt/core.h" #include "third-party/imgui/imgui.h" -TextureUploadHandler::TextureUploadHandler(const std::string& name, int my_id) - : BucketRenderer(name, my_id) {} +TextureUploadHandler::TextureUploadHandler(const std::string& name, + int my_id, + std::shared_ptr texture_animator) + : BucketRenderer(name, my_id), m_texture_animator(texture_animator) {} void TextureUploadHandler::render(DmaFollower& dma, SharedRenderState* render_state, @@ -25,7 +28,9 @@ void TextureUploadHandler::render(DmaFollower& dma, auto vif0 = dma.current_tag_vifcode0(); if (vif0.kind == VifCode::Kind::PC_PORT && vif0.immediate == 12) { dma.read_and_advance(); - render_state->texture_animator->handle_texture_anim_data(dma); + auto p = scoped_prof("texture-animator"); + m_texture_animator->handle_texture_anim_data(dma, (const u8*)render_state->ee_main_memory, + render_state->texture_pool.get()); } // does it look like data to do eye rendering? if (dma_tag.qwc == (128 / 16)) { @@ -63,6 +68,7 @@ void TextureUploadHandler::render(DmaFollower& dma, void TextureUploadHandler::flush_uploads(std::vector& uploads, SharedRenderState* render_state) { + auto p = scoped_prof("flush-uploads"); if (m_fake_uploads) { uploads.clear(); } else { @@ -73,7 +79,7 @@ void TextureUploadHandler::flush_uploads(std::vector& uploads, const u8* ee_mem = (const u8*)render_state->ee_main_memory; for (auto& upload : uploads) { render_state->texture_pool->handle_upload_now(ee_mem + upload.page, upload.mode, ee_mem, - render_state->offset_of_s7); + render_state->offset_of_s7, m_my_id == 999); } } } diff --git a/game/graphics/opengl_renderer/TextureUploadHandler.h b/game/graphics/opengl_renderer/TextureUploadHandler.h index c275a347d..13f6d124e 100644 --- a/game/graphics/opengl_renderer/TextureUploadHandler.h +++ b/game/graphics/opengl_renderer/TextureUploadHandler.h @@ -1,6 +1,7 @@ #pragma once #include "game/graphics/opengl_renderer/BucketRenderer.h" +#include "game/graphics/opengl_renderer/TextureAnimator.h" #include "game/graphics/texture/TexturePool.h" /*! @@ -10,7 +11,9 @@ */ class TextureUploadHandler : public BucketRenderer { public: - TextureUploadHandler(const std::string& name, int my_id); + TextureUploadHandler(const std::string& name, + int my_id, + std::shared_ptr texture_animator); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; @@ -22,4 +25,5 @@ class TextureUploadHandler : public BucketRenderer { void flush_uploads(std::vector& uploads, SharedRenderState* render_state); bool m_fake_uploads = false; int m_upload_count = 0; + std::shared_ptr m_texture_animator; }; diff --git a/game/graphics/opengl_renderer/foreground/Merc2.cpp b/game/graphics/opengl_renderer/foreground/Merc2.cpp index 62460f6c0..62c80f3fe 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.cpp +++ b/game/graphics/opengl_renderer/foreground/Merc2.cpp @@ -48,7 +48,8 @@ std::mutex g_merc_data_mutex; -Merc2::Merc2(ShaderLibrary& shaders) { +Merc2::Merc2(ShaderLibrary& shaders, const std::vector* anim_slot_array) + : m_anim_slot_array(anim_slot_array) { // Set up main vertex array. This will point to the data stored in the .FR3 level file, and will // be uploaded to the GPU by the Loader. glGenVertexArrays(1, &m_vao); @@ -1083,7 +1084,7 @@ Merc2::Draw* Merc2::alloc_normal_draw(const tfrag3::MercDraw& mdraw, // but don't toggle it the other way? } - draw->texture = mdraw.eye_id == 0xff ? mdraw.tree_tex_id : (0xffffff00 | mdraw.eye_id); + draw->texture = mdraw.eye_id == 0xff ? mdraw.tree_tex_id : (0xefffff00 | mdraw.eye_id); draw->first_bone = first_bone; draw->light_idx = lights; draw->num_triangles = mdraw.num_triangles; @@ -1233,15 +1234,18 @@ void Merc2::do_draws(const Draw* draw_array, fog_on = true; } bool use_mipmaps_for_filtering = true; - if ((int)draw.texture != last_tex) { - if (draw.texture < lev->textures.size()) { + if (draw.texture != last_tex) { + if (draw.texture < (int)lev->textures.size() && draw.texture >= 0) { glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture)); - } else if ((draw.texture & 0xffffff00) == 0xffffff00) { + } else if ((draw.texture & 0xffffff00) == 0xefffff00) { auto maybe_eye = render_state->eye_renderer->lookup_eye_texture(draw.texture & 0xff); if (maybe_eye) { glBindTexture(GL_TEXTURE_2D, *maybe_eye); } use_mipmaps_for_filtering = false; + } else if (draw.texture < 0) { + int slot = -(draw.texture + 1); + glBindTexture(GL_TEXTURE_2D, m_anim_slot_array->at(slot)); } else { fmt::print("Invalid draw.texture is {}, would have crashed.\n", draw.texture); } diff --git a/game/graphics/opengl_renderer/foreground/Merc2.h b/game/graphics/opengl_renderer/foreground/Merc2.h index e8487a950..b3ad2e8c2 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.h +++ b/game/graphics/opengl_renderer/foreground/Merc2.h @@ -40,7 +40,7 @@ struct MercDebugStats { class Merc2 { public: - Merc2(ShaderLibrary& shaders); + Merc2(ShaderLibrary& shaders, const std::vector* anim_slot_array); ~Merc2(); void draw_debug_window(MercDebugStats* stats); void render(DmaFollower& dma, @@ -50,6 +50,7 @@ class Merc2 { static constexpr int kMaxBlerc = 40; private: + const std::vector* m_anim_slot_array; enum MercDataMemory { LOW_MEMORY = 0, BUFFER_BASE = 442, @@ -182,7 +183,7 @@ class Merc2 { u32 first_index; u32 index_count; DrawMode mode; - u32 texture; + s32 texture; u32 num_triangles; u16 first_bone; u16 light_idx; diff --git a/game/graphics/opengl_renderer/loader/Loader.cpp b/game/graphics/opengl_renderer/loader/Loader.cpp index efd77e1a8..d45639019 100644 --- a/game/graphics/opengl_renderer/loader/Loader.cpp +++ b/game/graphics/opengl_renderer/loader/Loader.cpp @@ -249,7 +249,7 @@ void Loader::loader_thread() { * Load a "common" FR3 file that has non-level textures. * This should be called during initialization, before any threaded loading goes on. */ -void Loader::load_common(TexturePool& tex_pool, const std::string& name) { +const tfrag3::Level& Loader::load_common(TexturePool& tex_pool, const std::string& name) { auto data = file_util::read_binary_file(m_base_path / fmt::format("{}.fr3", name)); auto decomp_data = compression::decompress_zstd(data.data(), data.size()); @@ -270,6 +270,7 @@ void Loader::load_common(TexturePool& tex_pool, const std::string& name) { while (!done) { done = mls.run(tim, input); } + return *m_common_level.level; } bool Loader::upload_textures(Timer& timer, LevelData& data, TexturePool& texture_pool) { diff --git a/game/graphics/opengl_renderer/loader/Loader.h b/game/graphics/opengl_renderer/loader/Loader.h index 689de037a..03084113e 100644 --- a/game/graphics/opengl_renderer/loader/Loader.h +++ b/game/graphics/opengl_renderer/loader/Loader.h @@ -22,7 +22,7 @@ class Loader { void update_blocking(TexturePool& tex_pool); const LevelData* get_tfrag3_level(const std::string& level_name); std::optional get_merc_model(const char* model_name); - void load_common(TexturePool& tex_pool, const std::string& name); + const tfrag3::Level& load_common(TexturePool& tex_pool, const std::string& name); void set_want_levels(const std::vector& levels); std::vector get_in_use_levels(); void draw_debug_window(); diff --git a/game/graphics/opengl_renderer/loader/LoaderStages.cpp b/game/graphics/opengl_renderer/loader/LoaderStages.cpp index 6386db713..380e2dd2d 100644 --- a/game/graphics/opengl_renderer/loader/LoaderStages.cpp +++ b/game/graphics/opengl_renderer/loader/LoaderStages.cpp @@ -11,13 +11,11 @@ constexpr float LOAD_BUDGET = 2.5f; */ u64 add_texture(TexturePool& pool, const tfrag3::Texture& tex, bool is_common) { GLuint gl_tex; + glActiveTexture(GL_TEXTURE0); glGenTextures(1, &gl_tex); glBindTexture(GL_TEXTURE_2D, gl_tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.w, tex.h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, tex.data.data()); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, gl_tex); glGenerateMipmap(GL_TEXTURE_2D); float aniso = 0.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); diff --git a/game/graphics/opengl_renderer/opengl_utils.cpp b/game/graphics/opengl_renderer/opengl_utils.cpp index 4c5464768..001485fa9 100644 --- a/game/graphics/opengl_renderer/opengl_utils.cpp +++ b/game/graphics/opengl_renderer/opengl_utils.cpp @@ -71,6 +71,9 @@ FramebufferTexturePair::FramebufferTexturePair(int w, int h, u64 texture_format, } FramebufferTexturePair::~FramebufferTexturePair() { + if (m_moved_from) { + return; + } glDeleteFramebuffers(m_framebuffers.size(), m_framebuffers.data()); glDeleteTextures(1, &m_texture); } diff --git a/game/graphics/opengl_renderer/opengl_utils.h b/game/graphics/opengl_renderer/opengl_utils.h index 07efa9621..79d971e5a 100644 --- a/game/graphics/opengl_renderer/opengl_utils.h +++ b/game/graphics/opengl_renderer/opengl_utils.h @@ -17,14 +17,35 @@ class FramebufferTexturePair { GLuint texture() const { return m_texture; } + void update_texture_size(int w, int h) { + m_w = w; + m_h = h; + } + + void update_texture_unsafe(GLuint texture) { m_texture = texture; } + FramebufferTexturePair(const FramebufferTexturePair&) = delete; FramebufferTexturePair& operator=(const FramebufferTexturePair&) = delete; + FramebufferTexturePair(FramebufferTexturePair&& other) { + if (this == &other) { + return; + } + ASSERT(!m_moved_from && !other.m_moved_from); + other.m_moved_from = true; + m_w = other.m_w; + m_h = other.m_h; + m_texture = other.m_texture; + m_framebuffers = std::move(other.m_framebuffers); + } + int width() const { return m_w; } + int height() const { return m_h; } private: friend class FramebufferTexturePairContext; std::vector m_framebuffers; GLuint m_texture; int m_w, m_h; + bool m_moved_from = false; }; class FramebufferTexturePairContext { diff --git a/game/graphics/opengl_renderer/shaders/direct_basic_textured.frag b/game/graphics/opengl_renderer/shaders/direct_basic_textured.frag index 7035a55fb..2c57ba022 100644 --- a/game/graphics/opengl_renderer/shaders/direct_basic_textured.frag +++ b/game/graphics/opengl_renderer/shaders/direct_basic_textured.frag @@ -6,7 +6,8 @@ in vec4 fragment_color; in vec3 tex_coord; flat in uint use_uv; in vec4 gs_scissor; -uniform float alpha_reject; +uniform float alpha_min; +uniform float alpha_max; uniform float color_mult; uniform float alpha_mult; uniform float alpha_sub; @@ -82,7 +83,7 @@ void main() { color *= 2; color.xyz *= color_mult; color.w *= alpha_mult; - if (color.a < alpha_reject) { + if (color.a < alpha_min || color.a > alpha_max) { discard; } if (tex_info.w == 1) { diff --git a/game/graphics/opengl_renderer/shaders/tex_anim.frag b/game/graphics/opengl_renderer/shaders/tex_anim.frag new file mode 100644 index 000000000..ce98b70ed --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/tex_anim.frag @@ -0,0 +1,31 @@ +#version 430 core + +out vec4 color; + +uniform vec4 rgba; +uniform int enable_tex; +uniform int tcc; +uniform ivec4 channel_scramble; + +in vec2 uv; + +layout (binding = 0) uniform sampler2D tex; + +void main() { + + if (enable_tex == 1) { + vec4 tex_color = texture(tex, uv); + vec4 unscambled_tex = vec4(tex_color[channel_scramble[0]], + tex_color[channel_scramble[1]], + tex_color[channel_scramble[2]], + tex_color[channel_scramble[3]]); + color = rgba / 128.; + if (tcc == 1) { + color *= unscambled_tex; + } else { + color.xyz *= unscambled_tex.xyz; + } + } else { + color = (rgba / 128.); + } +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/tex_anim.vert b/game/graphics/opengl_renderer/shaders/tex_anim.vert new file mode 100644 index 000000000..22a9de4f4 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/tex_anim.vert @@ -0,0 +1,15 @@ +#version 430 core + +layout (location = 0) in int vertex_index; + +uniform vec3 positions[4]; +uniform vec2 uvs[4]; +uniform vec4 rgba; +// TODO flags and stuff + +out vec2 uv; + +void main() { + gl_Position = vec4(-1. + (positions[vertex_index] * 2), 1); + uv = uvs[vertex_index]; +} diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 78c62a1c0..ab5744fbd 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -697,7 +697,7 @@ void gl_texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) { // just pass it to the texture pool. // the texture pool will take care of locking. // we don't want to lock here for the entire duration of the conversion. - g_gfx_data->texture_pool->handle_upload_now(tpage, mode, g_ee_main_mem, s7_ptr); + g_gfx_data->texture_pool->handle_upload_now(tpage, mode, g_ee_main_mem, s7_ptr, false); } } diff --git a/game/graphics/texture/TextureConverter.cpp b/game/graphics/texture/TextureConverter.cpp index cdc424a40..bc8dd14e9 100644 --- a/game/graphics/texture/TextureConverter.cpp +++ b/game/graphics/texture/TextureConverter.cpp @@ -25,6 +25,16 @@ void TextureConverter::upload(const u8* data, u32 dest, u32 size_vram_words) { } } +void TextureConverter::upload_width(const u8* data, u32 dest, u32 width, u32 height) { + for (u32 y = 0; y < height; y++) { + for (u32 x = 0; x < width; x++) { + // VRAM address (bytes) + auto addr32 = psmct32_addr(x, y, width) + dest * 256; + *(u32*)(m_vram.data() + addr32) = *((const u32*)(data) + (x + y * width)); + } + } +} + void TextureConverter::download_rgba8888(u8* result, u32 vram_addr, u32 goal_tex_width, @@ -199,7 +209,3 @@ void TextureConverter::download_rgba8888(u8* result, ASSERT(out_offset == expected_size_bytes); } - -void TextureConverter::serialize(Serializer& ser) { - ser.from_pod_vector(&m_vram); -} diff --git a/game/graphics/texture/TextureConverter.h b/game/graphics/texture/TextureConverter.h index a55376405..d81ceaa48 100644 --- a/game/graphics/texture/TextureConverter.h +++ b/game/graphics/texture/TextureConverter.h @@ -3,12 +3,12 @@ #include #include "common/common_types.h" -#include "common/util/Serializer.h" class TextureConverter { public: TextureConverter(); void upload(const u8* data, u32 dest, u32 size_vram_words); + void upload_width(const u8* data, u32 dest, u32 width, u32 height); void download_rgba8888(u8* result, u32 vram_addr, u32 goal_tex_width, @@ -18,7 +18,6 @@ class TextureConverter { u32 clut_psm, u32 clut_vram_addr, u32 expected_size_bytes); - void serialize(Serializer& ser); private: std::vector m_vram; diff --git a/game/graphics/texture/TextureID.h b/game/graphics/texture/TextureID.h new file mode 100644 index 000000000..7ffdb4864 --- /dev/null +++ b/game/graphics/texture/TextureID.h @@ -0,0 +1,15 @@ +#pragma once + +#include "common/common_types.h" + +struct PcTextureId { + u16 page = -1; + u16 tex = -1; + + PcTextureId(u16 p, u16 t) : page(p), tex(t) {} + PcTextureId() = default; + + static PcTextureId from_combo_id(u32 val) { return PcTextureId(val >> 16, val & 0xffff); } + + bool operator==(const PcTextureId& other) const { return page == other.page && tex == other.tex; } +}; diff --git a/game/graphics/texture/TexturePool.cpp b/game/graphics/texture/TexturePool.cpp index b718d02c8..a1d7e2bea 100644 --- a/game/graphics/texture/TexturePool.cpp +++ b/game/graphics/texture/TexturePool.cpp @@ -110,6 +110,21 @@ void TexturePool::move_existing_to_vram(GpuTexture* tex, u32 slot_addr) { } } +void TexturePool::update_gl_texture(GpuTexture* gpu_texture, + u32 new_w, + u32 new_h, + GLuint new_gl_texture) { + ASSERT(gpu_texture->gpu_textures.size() == 1); + gpu_texture->gpu_textures[0].gl = new_gl_texture; + gpu_texture->w = new_w; + gpu_texture->h = new_h; + for (int si : gpu_texture->slots) { + auto& slot = m_textures[si]; + ASSERT(slot.source == gpu_texture); + slot.gpu_texture = new_gl_texture; + } +} + void TexturePool::refresh_links(GpuTexture& texture) { u64 tex_to_use = texture.is_placeholder ? m_placeholder_texture_id : texture.gpu_textures.front().gl; @@ -174,7 +189,11 @@ void GpuTexture::add_slot(u32 slot) { * We could store textures in the right format to begin with, or spread the conversion out over * multiple frames. */ -void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr) { +void TexturePool::handle_upload_now(const u8* tpage, + int mode, + const u8* memory_base, + u32 s7_ptr, + bool debug) { std::unique_lock lk(m_mutex); // extract the texture-page object. This is just a description of the page data. GoalTexturePage texture_page; @@ -201,6 +220,12 @@ void TexturePool::handle_upload_now(const u8* tpage, int mode, const u8* memory_ for (int tex_idx = 0; tex_idx < texture_page.length; tex_idx++) { GoalTexture tex; if (texture_page.try_copy_texture_description(&tex, tex_idx, memory_base, tpage, s7_ptr)) { + if (debug) { + fmt::print("Pool upload {} to {}\n", + std::string(goal_string(texture_page.name_ptr, memory_base)) + + goal_string(tex.name_ptr, memory_base), + tex.dest[0]); + } // each texture may have multiple mip levels. for (int mip_idx = 0; mip_idx < tex.num_mips; mip_idx++) { if (has_segment[tex.segment_of_mip(mip_idx)]) { diff --git a/game/graphics/texture/TexturePool.h b/game/graphics/texture/TexturePool.h index af2b1eb36..37a45e97a 100644 --- a/game/graphics/texture/TexturePool.h +++ b/game/graphics/texture/TexturePool.h @@ -14,6 +14,7 @@ #include "game/graphics/pipelines/opengl.h" #include "game/graphics/texture/TextureConverter.h" +#include "game/graphics/texture/TextureID.h" // verify all texture lookups. // will make texture lookups slower and likely caused dropped frames when loading @@ -72,18 +73,6 @@ constexpr int SKY_TEXTURE_VRAM_ADDRS[2] = {8064, 8096}; * The game will inform us when it uploads to VRAM */ -struct PcTextureId { - u16 page = -1; - u16 tex = -1; - - PcTextureId(u16 p, u16 t) : page(p), tex(t) {} - PcTextureId() = default; - - static PcTextureId from_combo_id(u32 val) { return PcTextureId(val >> 16, val & 0xffff); } - - bool operator==(const PcTextureId& other) const { return page == other.page && tex == other.tex; } -}; - template class TextureMap { public: @@ -303,10 +292,11 @@ struct GoalTexturePage { class TexturePool { public: TexturePool(GameVersion version); - void handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr); + void handle_upload_now(const u8* tpage, int mode, const u8* memory_base, u32 s7_ptr, bool debug); GpuTexture* give_texture(const TextureInput& in); GpuTexture* give_texture_and_load_to_vram(const TextureInput& in, u32 vram_slot); void unload_texture(PcTextureId tex_id, u64 gpu_id); + void update_gl_texture(GpuTexture* texture, u32 new_w, u32 new_h, GLuint new_gl_texture); /*! * Look up an OpenGL texture by vram address. Return std::nullopt if the game hasn't loaded diff --git a/game/graphics/texture/jak1_tpage_dir.h b/game/graphics/texture/jak1_tpage_dir.h index 0bc48fdaa..dddd6a342 100644 --- a/game/graphics/texture/jak1_tpage_dir.h +++ b/game/graphics/texture/jak1_tpage_dir.h @@ -5,4 +5,4 @@ #include "common/common_types.h" const std::vector& get_jak1_tpage_dir(); -constexpr u32 EXTRA_PC_PORT_TEXTURE_COUNT = 50; \ No newline at end of file +constexpr u32 EXTRA_PC_PORT_TEXTURE_COUNT = 64; \ No newline at end of file diff --git a/game/graphics/texture/jak2_tpage_dir.h b/game/graphics/texture/jak2_tpage_dir.h index bffec3a6f..97947c9f5 100644 --- a/game/graphics/texture/jak2_tpage_dir.h +++ b/game/graphics/texture/jak2_tpage_dir.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "common/common_types.h" diff --git a/game/kernel/jak2/kdgo.cpp b/game/kernel/jak2/kdgo.cpp index 572ec3183..58df9d72e 100644 --- a/game/kernel/jak2/kdgo.cpp +++ b/game/kernel/jak2/kdgo.cpp @@ -1,7 +1,11 @@ #include "kdgo.h" #include "common/global_profiler/GlobalProfiler.h" +#include "common/link_types.h" #include "common/log/log.h" +#include "common/util/BitUtils.h" +#include "common/util/FileUtil.h" +#include "common/util/Timer.h" #include "game/kernel/common/Ptr.h" #include "game/kernel/common/fileio.h" @@ -45,6 +49,76 @@ void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) load_and_link_dgo_from_c(name, heap, flag, buffer_size, false); } +/*! + * Faster version of load_and_link_dgo_from_c that skips the IOP and reads the file directly + * to GOAL memory. + */ +void load_and_link_dgo_from_c_fast(const char* name, + Ptr heap, + u32 linkFlag, + s32 bufferSize) { + Timer timer; + lg::debug("[Load and Link DGO From C (fast)] {}", name); + + // append CGO if needed + char name_on_cd[16]; + kstrcpyup(name_on_cd, name); + if (name_on_cd[strlen(name_on_cd) - 4] != '.') { + strcat(name_on_cd, ".CGO"); + } + + // open the DGO file: + auto file_path = file_util::get_jak_project_dir() / "out" / game_version_names[g_game_version] / + "iso" / name_on_cd; + auto fp = fopen(file_path.string().c_str(), "rb"); + if (!fp) { + lg::die("Failed to open DGO: {}, path {}\n", name, file_path.string()); + } + + // allocate temporary buffers for linking: + auto old_heap_top = heap->top; + auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-1"); + + // read the header + DgoHeader header; + if (fread(&header, sizeof(DgoHeader), 1, fp) != 1) { + lg::die("failed to read dgo header"); + } + lg::info("got {} objects, name {}\n", header.object_count, header.name); + + // load all but the final + for (int i = 0; i < (int)header.object_count - 1; i++) { + if (fread(buffer1.c(), sizeof(ObjectHeader), 1, fp) != 1) { + lg::die("failed to read object header"); + } + auto* obj_header = (ObjectHeader*)buffer1.c(); + u32 aligned_size = align16(obj_header->size); + auto* obj_dest = buffer1.c() + sizeof(ObjectHeader); + if (fread(obj_dest, aligned_size, 1, fp) != 1) { + lg::die("Failed to read object data"); + } + link_and_exec(buffer1 + sizeof(ObjectHeader), obj_header->name, obj_header->size, heap, + linkFlag, true); + } + + auto final_object_dest = Ptr((heap->current + 0x3f).offset & 0xffffffc0); + if (fread(final_object_dest.c(), sizeof(ObjectHeader), 1, fp) != 1) { + lg::die("failed to read final object header"); + } + auto* obj_header = (ObjectHeader*)final_object_dest.c(); + u32 aligned_size = align16(obj_header->size); + auto* obj_dest = (final_object_dest + sizeof(ObjectHeader)).c(); + if (fread(obj_dest, aligned_size, 1, fp) != 1) { + lg::die("Failed to read object data"); + } + link_and_exec(final_object_dest + sizeof(ObjectHeader), obj_header->name, obj_header->size, heap, + linkFlag, true); + + heap->top = old_heap_top; + fclose(fp); + lg::info("load_and_link_dgo_from_c_fast took {:.3f} s\n", timer.getSeconds()); +} + /*! * Load and link a DGO file. * This does not use the mutli-threaded linker and will block until the entire file is done.e @@ -54,6 +128,7 @@ void load_and_link_dgo_from_c(const char* name, u32 linkFlag, s32 bufferSize, bool jump_from_c_to_goal) { + Timer timer; lg::debug("[Load and Link DGO From C] {}", name); u32 oldShowStall = sShowStallMsg; @@ -116,6 +191,7 @@ void load_and_link_dgo_from_c(const char* name, ContinueLoadingDGO(buffer1, buffer2, Ptr((heap->current + 0x3f).offset & 0xffffffc0)); } } + lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds()); sShowStallMsg = oldShowStall; } diff --git a/game/kernel/jak2/kdgo.h b/game/kernel/jak2/kdgo.h index 912a90186..f8cebd9a6 100644 --- a/game/kernel/jak2/kdgo.h +++ b/game/kernel/jak2/kdgo.h @@ -10,4 +10,8 @@ void load_and_link_dgo_from_c(const char* name, s32 bufferSize, bool jump_from_c_to_goal); void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); +void load_and_link_dgo_from_c_fast(const char* name, + Ptr heap, + u32 linkFlag, + s32 bufferSize); } // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kmachine.cpp b/game/kernel/jak2/kmachine.cpp index 1eec8c0af..34859145a 100644 --- a/game/kernel/jak2/kmachine.cpp +++ b/game/kernel/jak2/kmachine.cpp @@ -795,9 +795,12 @@ void InitMachineScheme() { *EnableMethodSet = *EnableMethodSet + 1; { auto p = scoped_prof("load-game-dgo"); - load_and_link_dgo_from_c("game", kglobalheap, - LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, - 0x400000, true); + // load_and_link_dgo_from_c("game", kglobalheap, + // LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | + // LINK_FLAG_PRINT_LOGIN, 0x400000, true); + load_and_link_dgo_from_c_fast( + "game", kglobalheap, LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, + 0x400000); } *EnableMethodSet = *EnableMethodSet + -1; @@ -833,6 +836,7 @@ void initialize_sql_db() { // Attempt to open the database const auto opened = sql_db.open_db(db_path.string()); + (void)opened; fs::path schema_file = file_util::get_jak_project_dir() / "goal_src" / "jak2" / "tools" / "editable-schema.sql"; diff --git a/game/overlord/jak2/iso_cd.cpp b/game/overlord/jak2/iso_cd.cpp index fd7310df3..a0082cb54 100644 --- a/game/overlord/jak2/iso_cd.cpp +++ b/game/overlord/jak2/iso_cd.cpp @@ -139,7 +139,7 @@ auto sceCdCallback(void (*callback)(int)) { int sceCdRead(int lsn, int num_sectors, void* dest, void* mode) { (void)mode; - auto do_read = [lsn, num_sectors, dest, mode](s32 thid) { + auto do_read = [lsn, num_sectors, dest](s32 thid) { // printf("sceCdRead %d, %d -> %p\n", lsn, num_sectors, dest); ASSERT(gFakeCd.fp); if (fseek(gFakeCd.fp, lsn * SECTOR_SIZE, SEEK_SET)) { diff --git a/game/system/hid/devices/keyboard.cpp b/game/system/hid/devices/keyboard.cpp index 7c48716be..ebe134b31 100644 --- a/game/system/hid/devices/keyboard.cpp +++ b/game/system/hid/devices/keyboard.cpp @@ -91,7 +91,7 @@ void KeyboardDevice::clear_actions(std::shared_ptr data) { void KeyboardDevice::process_event(const SDL_Event& event, const CommandBindingGroups& commands, - std::shared_ptr data, + std::shared_ptr /*data*/, std::optional& bind_assignment) { if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { const auto key_event = event.key; diff --git a/game/system/hid/devices/mouse.cpp b/game/system/hid/devices/mouse.cpp index dae83828f..c9e3a5fc0 100644 --- a/game/system/hid/devices/mouse.cpp +++ b/game/system/hid/devices/mouse.cpp @@ -61,7 +61,7 @@ void MouseDevice::poll_state(std::shared_ptr data) { data->update_analog_sim_tracker(false); ActiveMouseAction action; action.player_movement = true; - action.revert_action = [](std::shared_ptr data, InputBinding bind) { + action.revert_action = [](std::shared_ptr data, InputBinding /*bind*/) { data->analog_data.at(1) += 127; // stop moving forward data->update_analog_sim_tracker(true); }; diff --git a/game/tools/subtitle_editor/subtitle_editor.cpp b/game/tools/subtitle_editor/subtitle_editor.cpp index 0daf5887f..990955f41 100644 --- a/game/tools/subtitle_editor/subtitle_editor.cpp +++ b/game/tools/subtitle_editor/subtitle_editor.cpp @@ -48,7 +48,8 @@ void SubtitleEditor::draw_window() { } } else if (m_db_failed_to_load) { ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); - ImGui::Text(fmt::format("Error Loading - {}!", m_subtitle_db.m_load_error.value()).c_str()); + ImGui::Text("%s", + fmt::format("Error Loading - {}!", m_subtitle_db.m_load_error.value()).c_str()); ImGui::PopStyleColor(); if (ImGui::Button("Try Again")) { if (g_game_version == GameVersion::Jak1) { @@ -334,9 +335,10 @@ void SubtitleEditor::draw_all_non_cutscenes(bool base_cutscenes) { } } -std::string SubtitleEditor::subtitle_line_summary(const SubtitleLine& line, - const SubtitleLineMetadata& line_meta, - const std::shared_ptr bank) { +std::string SubtitleEditor::subtitle_line_summary( + const SubtitleLine& line, + const SubtitleLineMetadata& line_meta, + const std::shared_ptr /*bank*/) { // Truncate the text if it's too long, it's supposed to just be a summary at a glance std::string line_text = ""; if (!line.text.empty()) { diff --git a/goal_src/jak2/engine/gfx/sky/sky-tng.gc b/goal_src/jak2/engine/gfx/sky/sky-tng.gc index be90437e8..19c7bea98 100644 --- a/goal_src/jak2/engine/gfx/sky/sky-tng.gc +++ b/goal_src/jak2/engine/gfx/sky/sky-tng.gc @@ -826,11 +826,11 @@ vf31: cam 0 (premultiplied by hmge) (moon-dma (the-as sky-work s4-0) s3-0) (draw-haze (the-as sky-work s4-0) s3-0) (when (nonzero? *sky-texture-anim-array*) ;; added check - ; (draw-clouds (the-as sky-work s4-0) s3-0) ;; DISABLED! + (draw-clouds (the-as sky-work s4-0) s3-0) ;; DISABLED! ) (draw-base (the-as sky-work s4-0) s3-0) (when (nonzero? *sky-texture-anim-array*) ;; added check - ;(draw-fog (the-as sky-work s4-0) s3-0) + (draw-fog (the-as sky-work s4-0) s3-0) ) ) ) diff --git a/goal_src/jak2/engine/gfx/texture/texture-anim-funcs.gc b/goal_src/jak2/engine/gfx/texture/texture-anim-funcs.gc index 3a12c90ae..24f7d8dfa 100644 --- a/goal_src/jak2/engine/gfx/texture/texture-anim-funcs.gc +++ b/goal_src/jak2/engine/gfx/texture/texture-anim-funcs.gc @@ -51,9 +51,20 @@ (t0-1 (/ (-> v1-4 w) 2)) (a3-1 (/ (-> v1-4 h) 2)) ) - (upload-vram-data dma-buf (the-as int (-> v1-4 dest 0)) (the-as pointer (-> v1-4 pad 0)) a3-1 t0-1) + ;; modified + ;(upload-vram-data dma-buf (the-as int (-> v1-4 dest 0)) (the-as pointer (-> v1-4 pad 0)) a3-1 t0-1) + (pc-texture-anim-flag upload-generic-vram dma-buf :qwc 1) + (let ((upload-record (the texture-anim-pc-upload (-> dma-buf base)))) + (set! (-> upload-record data) (the-as pointer (-> v1-4 pad 0))) ;; the clut16x16 object + (set! (-> upload-record width) t0-1) + (set! (-> upload-record height) a3-1) + (set! (-> upload-record dest) (-> v1-4 dest 0)) + (set! (-> upload-record format) (gs-psm ct32)) + ) + (&+! (-> dma-buf base) 16) + ) - (dma-buffer-add-gs-set dma-buf (texflush 0)) + ;(dma-buffer-add-gs-set dma-buf (texflush 0)) don't need (texture-anim-layer-add-shader dma-buf layer 1) (texture-anim-layer-draw dma-buf width height layer) ) @@ -198,37 +209,64 @@ ) ) (let ((s5-1 (-> arg1 tex))) - (dma-buffer-add-gs-set arg0 - (bitbltbuf - (new 'static 'gs-bitbltbuf :dpsm (the-as int (-> s5-1 psm)) :dbp (-> s5-1 dest 0) :dbw (-> s5-1 width 0)) - ) - (trxpos (new 'static 'gs-trxpos)) - (trxreg (new 'static 'gs-trxreg :rrw (-> s5-1 w) :rrh (-> s5-1 h))) - (trxdir (new 'static 'gs-trxdir)) - ) - (dma-buffer-add-ref-texture arg0 (the-as pointer (-> s5-1 pad 0)) (-> s5-1 w) (-> s5-1 h) (-> s5-1 psm)) - (upload-vram-data arg0 (the-as int (-> s5-1 clutdest)) (the-as pointer (-> s5-1 pad 1)) 16 16) + + ;; they use bitbltbuf directly to upload in the format of the data, rather than + ;; the usual PSM32 trick + ; (dma-buffer-add-gs-set arg0 + ; (bitbltbuf + ; (new 'static 'gs-bitbltbuf :dpsm (the-as int (-> s5-1 psm)) :dbp (-> s5-1 dest 0) :dbw (-> s5-1 width 0)) + ; ) + ; (trxpos (new 'static 'gs-trxpos)) + ; (trxreg (new 'static 'gs-trxreg :rrw (-> s5-1 w) :rrh (-> s5-1 h))) + ; (trxdir (new 'static 'gs-trxdir)) + ; ) + ; (dma-buffer-add-ref-texture arg0 (the-as pointer (-> s5-1 pad 0)) (-> s5-1 w) (-> s5-1 h) (-> s5-1 psm)) + + (pc-texture-anim-flag upload-generic-vram arg0 :qwc 1) + (let ((upload-record (the texture-anim-pc-upload (-> arg0 base)))) + (set! (-> upload-record data) (the-as pointer (-> s5-1 pad 0))) ;; the texture data + (set! (-> upload-record width) (-> s5-1 w)) ;; see fog-texture-anim-init, there's not cropping. + (set! (-> upload-record height) (-> s5-1 h)) + (set! (-> upload-record dest) (-> s5-1 dest 0)) + (set! (-> upload-record format) (-> s5-1 psm)) + ) + (&+! (-> arg0 base) 16) + + ;; and the clut + ;(upload-vram-data arg0 (the-as int (-> s5-1 clutdest)) (the-as pointer (-> s5-1 pad 1)) 16 16) + + (pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1) + (let ((upload-record (the texture-anim-pc-upload (-> arg0 base)))) + (set! (-> upload-record data) (the-as pointer (-> s5-1 pad 1))) ;; the clut16x16 object + (set! (-> upload-record width) 16) + (set! (-> upload-record height) 16) + (set! (-> upload-record dest) (-> s5-1 clutdest)) + (set! (-> upload-record format) (gs-psm ct32)) + ) + (&+! (-> arg0 base) 16) + (set! (-> *texture-pool* ids (shr (-> s5-1 clutdest) 6)) (the-as uint 0)) - (let* ((v1-29 arg0) - (a0-51 (the-as object (-> v1-29 base))) - ) - (set! (-> (the-as dma-packet a0-51) dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt))) - (set! (-> (the-as dma-packet a0-51) vif0) (new 'static 'vif-tag)) - (set! (-> (the-as dma-packet a0-51) vif1) (new 'static 'vif-tag :imm #x2 :cmd (vif-cmd direct) :msk #x1)) - (set! (-> v1-29 base) (&+ (the-as pointer a0-51) 16)) - ) - (let* ((v1-30 arg0) - (a0-53 (the-as object (-> v1-30 base))) - ) - (set! (-> (the-as gs-gif-tag a0-53) tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x1)) - (set! (-> (the-as gs-gif-tag a0-53) regs) GIF_REGS_ALL_AD) - (set! (-> v1-30 base) (&+ (the-as pointer a0-53) 16)) - ) - (let ((v1-31 (-> arg0 base))) - (set! (-> (the-as (pointer int64) v1-31)) 0) - (set! (-> (the-as (pointer gs-reg64) v1-31) 1) (gs-reg64 texflush)) - (set! (-> arg0 base) (&+ v1-31 16)) - ) + ;; just a texflush. + ; (let* ((v1-29 arg0) + ; (a0-51 (the-as object (-> v1-29 base))) + ; ) + ; (set! (-> (the-as dma-packet a0-51) dma) (new 'static 'dma-tag :qwc #x2 :id (dma-tag-id cnt))) + ; (set! (-> (the-as dma-packet a0-51) vif0) (new 'static 'vif-tag)) + ; (set! (-> (the-as dma-packet a0-51) vif1) (new 'static 'vif-tag :imm #x2 :cmd (vif-cmd direct) :msk #x1)) + ; (set! (-> v1-29 base) (&+ (the-as pointer a0-51) 16)) + ; ) + ; (let* ((v1-30 arg0) + ; (a0-53 (the-as object (-> v1-30 base))) + ; ) + ; (set! (-> (the-as gs-gif-tag a0-53) tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x1)) + ; (set! (-> (the-as gs-gif-tag a0-53) regs) GIF_REGS_ALL_AD) + ; (set! (-> v1-30 base) (&+ (the-as pointer a0-53) 16)) + ; ) + ; (let ((v1-31 (-> arg0 base))) + ; (set! (-> (the-as (pointer int64) v1-31)) 0) + ; (set! (-> (the-as (pointer gs-reg64) v1-31) 1) (gs-reg64 texflush)) + ; (set! (-> arg0 base) (&+ v1-31 16)) + ; ) (let ((v1-34 (shr (-> s5-1 dest 0) 6))) (dotimes (a0-56 3) (set! (-> *texture-pool* ids (+ v1-34 a0-56)) (the-as uint 0)) diff --git a/goal_src/jak2/engine/gfx/texture/texture-anim.gc b/goal_src/jak2/engine/gfx/texture/texture-anim.gc index aaa48e6c3..6e0493383 100644 --- a/goal_src/jak2/engine/gfx/texture/texture-anim.gc +++ b/goal_src/jak2/engine/gfx/texture/texture-anim.gc @@ -166,10 +166,57 @@ ) ) +(defenum texture-anim-pc + (start-anim-array 12) + (finish-anim-array 13) + (erase-and-init 14) + (upload-clut-16-16 15) + (upload-generic-vram 16) + (set-shader 17) + (draw 18) + (move-rg-to-ba 19) + (set-clut-alpha 20) + (copy-clut-alpha 21) + (darkjak 22) + (prison-jak 23) + (oracle-jak 24) + (nest-jak 25) + (kor-transform 26) + ) + +(deftype texture-anim-pc-upload (structure) + ((data pointer) + (width uint16) + (height uint16) + (dest uint32) + (format gs-psm) + (pad uint8 3) + ) + :size-assert 16 + ) + +(deftype texture-anim-pc-texture-transform (structure) + ((src-tbp uint32) + (dest-tbp uint32) + (pad0 uint32) + (pad1 uint32) + ) + ) + +(defmacro pc-texture-anim-flag (kind buf &key (qwc 0)) + `(dma-buffer-add-cnt-vif2 + ,buf + ,qwc + (new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm (texture-anim-pc ,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))) - (if s5-0 + (when s5-0 + (pc-texture-anim-flag set-shader arg0) (dma-buffer-add-gs-set arg0 (tex0-1 (new 'static 'gs-tex0 :cld #x1 @@ -196,7 +243,8 @@ (defun texture-anim-layer-add-clut-shader ((arg0 dma-buffer) (arg1 texture-anim-layer) (arg2 int)) "Add DMA to set the GS to use the clut as a ct32 texture. This is used to abuse the GS to blend cluts by pretending they are ct32's." (let ((a1-1 (-> arg1 tex))) - (if a1-1 + (when a1-1 + (pc-texture-anim-flag set-shader arg0) (dma-buffer-add-gs-set arg0 (tex0-1 (new 'static 'gs-tex0 :tbw #x1 :tw #x4 :th #x4 :tcc arg2 :tbp0 (-> a1-1 clutdest))) (tex1-1 (new 'static 'gs-tex1)) @@ -215,6 +263,8 @@ Always draws a tristrip with two triangles. " (local-vars (v1-27 float) (sv-224 matrix) (sv-228 matrix) (sv-232 matrix) (sv-236 matrix)) + (pc-texture-anim-flag draw dma-buf) ;; added + (rlet ((acc :class vf) (vf0 :class vf) (vf1 :class vf) @@ -346,6 +396,8 @@ (defun blend-clut-texture-anim-layer-func ((dma-buf dma-buffer) (arg1 uint) (width int) (height int) (layer texture-anim-layer) (time float)) "Apply texture animation layer to the clut. This interpolates the layer, sets up the GS to _read_ from the given texture's clut as a 16x16 psm32, then does the draw." + + (format 0 "before blend-clut-texture-anim-layer-func ~X~%" (-> dma-buf base)) (when (and (>= time (-> layer start-time)) (>= (-> layer end-time) time)) (texture-anim-layer-interp layer time) (when (!= (-> layer interpolated-color w) 0.0) @@ -353,11 +405,26 @@ (texture-anim-layer-draw dma-buf 16 16 layer) ) ) + (format 0 "after blend-clut-texture-anim-layer-func ~X~%" (-> dma-buf base)) + 0 ) (defun move-rg-to-ba-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float)) "Some cursed texture drawing." + + ;; HACK added: + (pc-texture-anim-flag move-rg-to-ba dma-buf :qwc 1) + (let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base)))) + (set! (-> transform src-tbp) (-> layer tex dest 0)) + (set! (-> transform dest-tbp) (* 32 fbp-to-draw)) + (set! (-> transform pad0) 0) + (set! (-> transform pad1) 0) + ) + (&+! (-> dma-buf base) 16) + (return 0) + ;; end added + (-> layer tex) (let ((tw (log2 (* width 2))) (th (log2 height)) @@ -503,8 +570,123 @@ ) ) +(define-extern *sky-texture-anim-array* (texture-anim-array texture-anim)) +(define-extern *darkjak-texture-anim-array* (texture-anim-array texture-anim)) +(define-extern *jakb-prison-texture-anim-array* (texture-anim-array texture-anim)) +(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)) + + (defun update-texture-anim ((bucket bucket-id) (anim-array texture-anim-array)) "Generate all DMA to update all textures in the given list for the given bucket." + (cond + ((or (= anim-array *sky-texture-anim-array*) + ) + ;; for sky, we basically emulate the full thing + ;; (format *stdcon* "doing sky to bucket ~d~%" bucket) + ) + ((= 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. + ;; (format *stdcon* "doing darkjak~%") + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag darkjak dma-buf :qwc 1) + (let ((morph (-> anim-array array-data 0 frame-time)) + (vec (the vector (-> dma-buf base))) + ) + (set! (-> vec x) morph) + ) + (&+! (-> dma-buf base) 16) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + (return #f) + ) + + ((= anim-array *jakb-prison-texture-anim-array*) + ;; prison is simple, and we reimplemented it in C++. + ;; so we just have to send the frame-time value. + ;; (format *stdcon* "doing prison-jak~%") + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag prison-jak dma-buf :qwc 1) + (let ((morph (-> anim-array array-data 0 frame-time)) + (vec (the vector (-> dma-buf base))) + ) + (set! (-> vec x) morph) + ) + (&+! (-> dma-buf base) 16) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + (return #f) + ) + + ((= anim-array *darkjak-hires-texture-anim-array*) + ;; oracle is simple, and we reimplemented it in C++. + ;; so we just have to send the frame-time value. + ;; (format *stdcon* "doing oracle jak~%") + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag oracle-jak dma-buf :qwc 1) + (let ((morph (-> anim-array array-data 0 frame-time)) + (vec (the vector (-> dma-buf base))) + ) + (set! (-> vec x) morph) + ) + (&+! (-> dma-buf base) 16) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + (return #f) + ) + ((= anim-array *darkjak-hires-nest-texture-anim-array*) + ;; oracle is simple, and we reimplemented it in C++. + ;; so we just have to send the frame-time value. + ;; (format *stdcon* "doing nest jak~%") + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag nest-jak dma-buf :qwc 1) + (let ((morph (-> anim-array array-data 0 frame-time)) + (vec (the vector (-> dma-buf base))) + ) + (set! (-> vec x) morph) + ) + (&+! (-> dma-buf base) 16) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + (return #f) + ) + ((= anim-array *kor-transform-texture-anim-array*) + ;; kor is simple, and we reimplemented it in C++. + ;; so we just have to send the frame-time value. + ;; (format *stdcon* "doing kor~%") + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag kor-transform dma-buf :qwc 1) + (let ((morph (-> anim-array array-data 0 frame-time)) + (vec (the vector (-> dma-buf base))) + ) + (set! (-> vec x) morph) + ) + (&+! (-> dma-buf base) 16) + (pc-texture-anim-flag finish-anim-array dma-buf) + ) + (return #f) + ) + (else + (return #f) ;; HACK!!! + ) + ) ;; ;; (return #f) @@ -521,17 +703,14 @@ bucket ) ;; added: flag for the PC port to indicate start of texture anim data: - (dma-buffer-add-cnt-vif2 - dma-buf - 0 - (new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm 12) - (new 'static 'vif-tag :cmd (vif-cmd pc-port)) - ) + (pc-texture-anim-flag start-anim-array dma-buf) + ;; loop over animated textures. Each will produce a single texture. (dotimes (anim-idx (-> anim-array length)) (let* ((anim (-> anim-array array-data anim-idx)) (dest-tex (-> anim tex)) ) + ; (format 0 "texture anim dest tex: ~A~%" dest-tex) (when dest-tex 0 (let ((tex-width (-> dest-tex w))) @@ -543,7 +722,7 @@ fbp-for-tex ) (else - ;; animating the clut. + ;; not really sure why, but for non-mt8h and non-ct32's we clear the clut here... (set! fbp-for-tex (shr (-> dest-tex clutdest) 5)) (set! tex-width 16) (set! tex-height 16) @@ -551,7 +730,9 @@ ) ) (when (and (nonzero? tex-width) (nonzero? tex-height)) + ; (format 0 " clearing~%") ;; configure for drawing to this texture. + (pc-texture-anim-flag erase-and-init dma-buf) (dma-buffer-add-gs-set-flusha dma-buf (scissor-1 (new 'static 'gs-scissor :scax1 (+ tex-width -1) :scay1 (+ tex-height -1))) (xyoffset-1 (new 'static 'gs-xy-offset :ofx #x8000 :ofy #x8000)) @@ -605,6 +786,7 @@ (set! layer-idx 0) (while (< layer-idx (the-as int (-> anim num-layers))) (let ((layer (-> anim data layer-idx))) + ; (format 0 " running layer ~D, dma at #x~X~%" layer-idx (-> dma-buf base)) ;; generate DMA for this layer's effect ((-> layer func) dma-buf fbp-for-tex tex-width tex-height layer (-> anim frame-time)) ) @@ -612,7 +794,9 @@ ) ) ;; some sort of final function (TODO args here) - (if (-> anim func) + (when (-> anim func) + ; (format 0 " running final, dma at #x~X~%" layer-idx (-> dma-buf base)) + ((-> anim func) dma-buf anim) ) @@ -632,13 +816,13 @@ ) ;; reset GS registers - we messed with frame/scissor. - (reset-display-gs-state *display* dma-buf) + ;(reset-display-gs-state *display* dma-buf) ;; added: flag for the PC port to indicate end of texture anim data: (dma-buffer-add-cnt-vif2 dma-buf 0 - (new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm 13) + (new 'static 'vif-tag :cmd (vif-cmd pc-port) :imm (texture-anim-pc finish-anim-array)) (new 'static 'vif-tag :cmd (vif-cmd pc-port)) ) ) @@ -725,6 +909,17 @@ (defun copy-clut-alpha-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float)) "Copy only alpha from the layer source clut." + + (pc-texture-anim-flag copy-clut-alpha dma-buf :qwc 1) + (let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base)))) + (set! (-> transform src-tbp) (-> layer tex dest 0)) + (set! (-> transform dest-tbp) (* 32 fbp-to-draw)) + (set! (-> transform pad0) 0) + (set! (-> transform pad1) 0) + ) + (&+! (-> dma-buf base) 16) + (return 0) + (when (and (>= time (-> layer start-time)) (>= (-> layer end-time) time)) (texture-anim-layer-interp layer time) (let ((s4-0 1)) @@ -831,6 +1026,19 @@ (defun set-clut-alpha-texture-anim-layer-func ((dma-buf dma-buffer) (fbp-to-draw uint) (width int) (height int) (layer texture-anim-layer) (time float)) "Set clut alpha to 128." + + ;; PC version + (pc-texture-anim-flag set-clut-alpha dma-buf :qwc 1) + (let ((transform (the texture-anim-pc-texture-transform (-> dma-buf base)))) + (set! (-> transform src-tbp) (* 32 fbp-to-draw)) + (set! (-> transform dest-tbp) (* 32 fbp-to-draw)) + (set! (-> transform pad0) 0) + (set! (-> transform pad1) 0) + ) + (&+! (-> dma-buf base) 16) + (return 0) + + (let ((v1-0 1)) (dma-buffer-add-gs-set dma-buf (test-1 (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always))) @@ -1060,7 +1268,18 @@ "Upload the clut for the clouds." (when (-> arg1 tex) (make-cloud-clut (the-as (pointer uint32) (-> arg1 tex pad 0)) (-> arg1 extra y) (-> arg1 extra z)) - (upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16) + + ;(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16) + (pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1) + (let ((upload-record (the texture-anim-pc-upload (-> arg0 base)))) + (set! (-> upload-record data) (the-as pointer (-> arg1 tex pad 0))) ;; the clut16x16 object + (set! (-> upload-record width) 16) + (set! (-> upload-record height) 16) + (set! (-> upload-record dest) (-> arg1 tex clutdest)) + (set! (-> upload-record format) (gs-psm ct32)) + ) + (&+! (-> arg0 base) 16) + (set! (-> *texture-pool* ids (shr (-> arg1 tex clutdest) 6)) (the-as uint 0)) 0 ) @@ -1202,7 +1421,18 @@ (defun texture-anim-alpha-ramp-clut-upload ((arg0 dma-buffer) (arg1 texture-anim)) (when (-> arg1 tex) - (upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16) + ;(upload-vram-data arg0 (the-as int (-> arg1 tex clutdest)) (the-as pointer (-> arg1 tex pad 0)) 16 16) + + (pc-texture-anim-flag upload-clut-16-16 arg0 :qwc 1) + (let ((upload-record (the texture-anim-pc-upload (-> arg0 base)))) + (set! (-> upload-record data) (the-as pointer (-> arg1 tex pad 0))) ;; the clut16x16 object + (set! (-> upload-record width) 16) + (set! (-> upload-record height) 16) + (set! (-> upload-record dest) (-> arg1 tex clutdest)) + (set! (-> upload-record format) (gs-psm ct32)) + ) + (&+! (-> arg0 base) 16) + (set! (-> *texture-pool* ids (shr (-> arg1 tex clutdest) 6)) (the-as uint 0)) 0 )