diff --git a/.github/workflows/release-pipeline.yaml b/.github/workflows/release-pipeline.yaml index c6ca628ab4..0ed5663907 100644 --- a/.github/workflows/release-pipeline.yaml +++ b/.github/workflows/release-pipeline.yaml @@ -86,7 +86,7 @@ jobs: uses: ./.github/workflows/macos-build.yaml with: cmakePreset: "Release-macos-x86_64-clang-static" - cachePrefix: "static" + cachePrefix: "static-arm" uploadArtifacts: true secrets: inherit @@ -150,13 +150,13 @@ jobs: - name: Prepare ARM macOS Build Assets run: | mkdir -p ./ci-artifacts/macos-arm - ./.github/scripts/releases/extract_build_unix.sh ./ci-artifacts/macos-arm ./ci-artifacts/opengoal-macos-arm-static ./ + ./.github/scripts/releases/extract_build_unix.sh ./ci-artifacts/macos-arm ./ci-artifacts/opengoal-macos-static-arm ./ pushd ci-artifacts/macos-arm TAG_VAL=${{ needs.cut_release.outputs.new_tag }} tar czf ../final/opengoal-macos-arm-${TAG_VAL}.tar.gz . popd - chmod +x ./ci-artifacts/opengoal-macos-arm-static/lsp/lsp - cp ./ci-artifacts/opengoal-macos-arm-static/lsp/lsp ./ci-artifacts/final/opengoal-lsp-macos-arm-${TAG_VAL}.bin + chmod +x ./ci-artifacts/opengoal-macos-static-arm/lsp/lsp + cp ./ci-artifacts/opengoal-macos-static-arm/lsp/lsp ./ci-artifacts/final/opengoal-lsp-macos-arm-${TAG_VAL}.bin - name: Upload Assets env: diff --git a/common/util/gltf_util.cpp b/common/util/gltf_util.cpp index f9e549d0c5..1a8f4de957 100644 --- a/common/util/gltf_util.cpp +++ b/common/util/gltf_util.cpp @@ -684,19 +684,12 @@ bool material_has_envmap(const tinygltf::Material& mat) { return mat.extensions.contains("KHR_materials_specular"); } -bool envmap_is_valid(const tinygltf::Material& mat, bool die) { +bool envmap_is_valid(const tinygltf::Material& mat) { if (material_has_envmap(mat) && mat.pbrMetallicRoughness.metallicRoughnessTexture.index < 0) { - std::string error = fmt::format( - "Material \"{}\" has specular property set, but is missing a metallic roughness texture! " - "Check " - "that the Specular IOR level for the material is at the default of 0.5 if this is " - "unintended.", - mat.name); - if (die) { - lg::die(error); - } else { - lg::error(error); - } + lg::warn(fmt::format( + "Material \"{}\" has specular property set, but is missing a metallic roughness texture, " + "ignoring envmap!", + mat.name)); return false; } return true; diff --git a/common/util/gltf_util.h b/common/util/gltf_util.h index 01cfbce124..ae0ca8be5e 100644 --- a/common/util/gltf_util.h +++ b/common/util/gltf_util.h @@ -126,7 +126,7 @@ struct EnvmapSettings { EnvmapSettings envmap_settings_from_gltf(const tinygltf::Material& mat); bool material_has_envmap(const tinygltf::Material& mat); -bool envmap_is_valid(const tinygltf::Material& mat, bool die); +bool envmap_is_valid(const tinygltf::Material& mat); /*! * Find the index of the skin for this model. Returns nullopt if there is no skin, the index of the diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 9f502a7c6a..cd25e4fe12 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -112,10 +112,12 @@ void extract_art_groups_from_level(const ObjectFileDB& db, std::map& art_group_data) { if (db.obj_files_by_dgo.count(dgo_name)) { const auto& files = db.obj_files_by_dgo.at(dgo_name); + MercSwapInfo swapped_info; for (const auto& file : files) { if (file.name.length() > 3 && !file.name.compare(file.name.length() - 3, 3, "-ag")) { const auto& ag_file = db.lookup_record(file); - extract_merc(ag_file, tex_db, db.dts, tex_remap, level_data, false, db.version()); + extract_merc(ag_file, tex_db, db.dts, tex_remap, level_data, false, db.version(), + swapped_info); extract_joint_group(ag_file, db.dts, db.version(), art_group_data); } } diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index ac4712f940..202923a617 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -10,6 +10,40 @@ namespace decompiler { +// info about what models have been replaced/added per level +struct MercSwapInfo { + std::map> per_level_merc_swaps; + std::map> per_level_custom_mdls; + + bool already_swapped(const std::string& model, const std::string& level) { + auto mdls_it = per_level_merc_swaps.find(level); + if (mdls_it != per_level_merc_swaps.end()) { + auto& mdls = mdls_it->second; + auto mdl = std::find(mdls.begin(), mdls.end(), model); + return mdl != mdls.end(); + } + return false; + } + + bool already_added(const std::string& model, const std::string& level) { + auto mdls_it = per_level_custom_mdls.find(level); + if (mdls_it != per_level_custom_mdls.end()) { + auto& mdls = mdls_it->second; + auto mdl = std::find(mdls.begin(), mdls.end(), model); + return mdl != mdls.end(); + } + return false; + } + + void add_to_swapped_list(const std::string& model, const std::string& level) { + per_level_merc_swaps[level].push_back(model); + } + + void add_to_custom_list(const std::string& model, const std::string& level) { + per_level_custom_mdls[level].push_back(model); + } +}; + // extract everything void extract_all_levels(const ObjectFileDB& db, const TextureDB& tex_db, diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index f5f5e1fa5d..e88b4fd0aa 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -1669,7 +1669,8 @@ void extract_merc(const ObjectFileData& ag_data, const std::vector& map, tfrag3::Level& out, bool dump_level, - GameVersion version) { + GameVersion version, + MercSwapInfo& swapped_info) { if (dump_level) { file_util::create_dir_if_needed(file_util::get_file_path({"debug_out/merc"})); } @@ -1795,31 +1796,43 @@ void extract_merc(const ObjectFileData& ag_data, } } - // do model replacements if present - auto merc_replacement_folder = file_util::get_jak_project_dir() / "custom_assets" / - game_version_names[version] / "merc_replacements"; - if (file_util::file_exists(merc_replacement_folder.string())) { - auto merc_replacements = - file_util::find_files_in_dir(merc_replacement_folder, std::regex(".*\\.glb")); - for (auto& path : merc_replacements) { - auto name = path.stem().string(); - auto it = std::find_if(out.merc_data.models.begin(), out.merc_data.models.end(), - [&](const auto& m) { return m.name == name; }); - if (it != out.merc_data.models.end()) { - auto& model = *it; - replace_model(out, model, path); + // do model replacement if present + for (auto& ctrl : ctrls) { + auto merc_replacements_path = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[version] / "merc_replacements"; + if (!swapped_info.already_swapped(ctrl.name, out.level_name)) { + if (file_util::file_exists(merc_replacements_path.string())) { + std::string file_name(ctrl.name + ".glb"); + auto mdl_path = merc_replacements_path / file_name; + if (file_util::file_exists(mdl_path.string())) { + auto it = std::find_if(out.merc_data.models.begin(), out.merc_data.models.end(), + [&](const auto& m) { return m.name == ctrl.name; }); + if (it != out.merc_data.models.end()) { + auto& model = *it; + replace_model(out, model, mdl_path); + swapped_info.add_to_swapped_list(ctrl.name, out.level_name); + } + } + } else { + lg::info("{} in level {} was already swapped, skipping", ctrl.name, out.level_name); } } - } - // add custom models if present - auto lvl_name = out.level_name == "" ? "common" : out.level_name; - auto models_folder = file_util::get_jak_project_dir() / "custom_assets" / - game_version_names[version] / "models" / lvl_name; - if (file_util::file_exists(models_folder.string())) { - auto custom_models = file_util::find_files_in_dir(models_folder, std::regex(".*\\.glb")); - for (auto& mdl : custom_models) { - add_custom_model_to_level(out, mdl.stem().string(), mdl); + // add custom models if present + auto lvl_name = out.level_name == "" ? "common" : out.level_name; + auto models_folder = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[version] / "models" / lvl_name; + if (file_util::file_exists(models_folder.string())) { + auto custom_models = file_util::find_files_in_dir(models_folder, std::regex(".*\\.glb")); + for (auto& mdl : custom_models) { + auto name = mdl.stem().string(); + if (!swapped_info.already_added(name, lvl_name)) { + add_custom_model_to_level(out, name, mdl); + swapped_info.add_to_custom_list(name, lvl_name); + } else { + lg::info("custom model {} was already added to level {}, skipping", name, lvl_name); + } + } } } } diff --git a/decompiler/level_extractor/extract_merc.h b/decompiler/level_extractor/extract_merc.h index 0332c88e57..3a64620a35 100644 --- a/decompiler/level_extractor/extract_merc.h +++ b/decompiler/level_extractor/extract_merc.h @@ -1,5 +1,7 @@ #pragma once +#include "extract_level.h" + #include "common/custom_data/Tfrag3Data.h" #include "decompiler/ObjectFile/ObjectFileDB.h" @@ -14,5 +16,6 @@ void extract_merc(const ObjectFileData& ag_data, const std::vector& map, tfrag3::Level& out, bool dump_level, - GameVersion version); + GameVersion version, + MercSwapInfo& swapped_info); } // namespace decompiler \ No newline at end of file diff --git a/decompiler/level_extractor/merc_replacement.cpp b/decompiler/level_extractor/merc_replacement.cpp index c35647e2e3..d79a1acbf8 100644 --- a/decompiler/level_extractor/merc_replacement.cpp +++ b/decompiler/level_extractor/merc_replacement.cpp @@ -17,7 +17,6 @@ void extract(const std::string& name, std::map draw_by_material; int mesh_count = 0; int prim_count = 0; - bool has_envmaps = false; int joints = 3; auto skin_idx = find_single_skin(model, all_nodes); if (skin_idx) { @@ -80,8 +79,11 @@ void extract(const std::string& name, tfrag3::MercEffect e; tfrag3::MercEffect envmap_eff; + envmap_eff.has_envmap = false; out.new_model.name = name; - out.new_model.max_bones = joints; + // if we have a skeleton, use that joint count, otherwise use a high default value since the model + // we replace can have more + out.new_model.max_bones = joints != 3 ? joints : 100; out.new_model.max_draws = 0; auto process_normal_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { @@ -161,11 +163,9 @@ void extract(const std::string& name, for (const auto& [mat_idx, d_] : draw_by_material) { const auto& mat = model.materials[mat_idx]; - if (!material_has_envmap(mat)) { + if (!material_has_envmap(mat) || !envmap_is_valid(mat)) { process_normal_draw(e, mat_idx, d_); } else { - envmap_is_valid(mat, true); - has_envmaps = true; envmap_eff.has_envmap = true; process_envmap_draw(envmap_eff, mat_idx, d_); } @@ -175,7 +175,7 @@ void extract(const std::string& name, if (!e.all_draws.empty()) { out.new_model.effects.push_back(e); } - if (has_envmaps) { + if (envmap_eff.has_envmap) { out.new_model.effects.push_back(envmap_eff); } diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index c1b4076212..c8acd7f0f8 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -188,10 +188,26 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { } if (!Gfx::g_debug_settings.ignore_hide_imgui) { - ImGui::Text("%s", fmt::format("Toggle toolbar with {}", - sdl_util::get_keyboard_button_name( - Gfx::g_debug_settings.hide_imgui_key, InputModifiers())) - .c_str()); + std::string button_text = + fmt::format("Click here or Press {} to hide Toolbar", + sdl_util::get_keyboard_button_name(Gfx::g_debug_settings.hide_imgui_key, + InputModifiers())); + + ImVec2 text_size = ImGui::CalcTextSize(button_text.c_str()); + float button_width = text_size.x + ImGui::GetStyle().FramePadding.x * 2; + float button_height = text_size.y + ImGui::GetStyle().FramePadding.y * 2; + + ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetStyleColorVec4(ImGuiCol_MenuBarBg)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, + ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive)); + + if (ImGui::Selectable(button_text.c_str(), false, ImGuiSelectableFlags_DontClosePopups, + ImVec2(button_width, button_height))) { + std::shared_ptr display = Display::GetMainDisplay(); + display->set_imgui_visible(false); + } + ImGui::PopStyleColor(3); } } ImGui::EndMainMenuBar(); diff --git a/game/sound/989snd/blocksound_handler.cpp b/game/sound/989snd/blocksound_handler.cpp index f1e189d5a0..375bb5e1ed 100644 --- a/game/sound/989snd/blocksound_handler.cpp +++ b/game/sound/989snd/blocksound_handler.cpp @@ -16,8 +16,14 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank, s32 sfx_vol, s32 sfx_pan, SndPlayParams& params, - u32 sound_id) - : m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank), m_sound_id(sound_id) { + u32 sound_id, + s32 start_tick) + : m_group(sfx.VolGroup), + m_sfx(sfx), + m_vm(vm), + m_bank(bank), + m_sound_id(sound_id), + m_start_tick(start_tick) { s32 vol, pan, pitch_mod, pitch_bend; if (sfx_vol == -1) { sfx_vol = sfx.Vol; @@ -298,4 +304,53 @@ void BlockSoundHandler::DoGrain() { m_countdown = m_sfx.Grains[m_next_grain].Delay + ret; } +SoundHandler* BlockSoundHandler::CheckInstanceLimit( + const std::map>& handlers, + s32 vol) { + if (!m_sfx.InstanceLimit) { + return nullptr; + } + + if (!m_sfx.Flags.has_instlimit()) { + return nullptr; + } + + BlockSoundHandler* weakest = nullptr; + int inst = 0; + + for (const auto& [id, handler_ptr] : handlers) { + // Only compare to BlockSoundHandlers + auto* handler = dynamic_cast(handler_ptr.get()); + if (!handler) { + continue; + } + + // See if this is playing the same sound + // 989snd checks both an orig_sound and a SH.Sound, but we never change the sound. + // We'd need to revisit this if we eventually support BRANCH grains. + if (&handler->m_sfx == &m_sfx) { + inst++; + if (!weakest || // + (m_sfx.Flags.instlimit_vol() && handler->m_app_volume < weakest->m_app_volume) || // + (m_sfx.Flags.instlimit_tick() && handler->m_start_tick < weakest->m_start_tick)) { + weakest = handler; + } + } + } + + // See if this handler would cause us to exceed the limit + if (m_sfx.InstanceLimit - 1 < inst) { + if (weakest && ((m_sfx.Flags.instlimit_vol() && weakest->m_app_volume < vol) || + m_sfx.Flags.instlimit_tick())) { + // existing weakest is worst + return weakest; + } else { + // new sound is weakest + return this; + } + } else { + return nullptr; + } +} + } // namespace snd diff --git a/game/sound/989snd/blocksound_handler.h b/game/sound/989snd/blocksound_handler.h index f1b30a80fa..146da0e02c 100644 --- a/game/sound/989snd/blocksound_handler.h +++ b/game/sound/989snd/blocksound_handler.h @@ -26,7 +26,8 @@ class BlockSoundHandler : public SoundHandler { s32 sfx_vol, s32 sfx_pan, SndPlayParams& params, - u32 sound_id); + u32 sound_id, + s32 start_tick); ~BlockSoundHandler() override; bool Tick() override; @@ -46,6 +47,9 @@ class BlockSoundHandler : public SoundHandler { void UpdatePitch(); + SoundHandler* CheckInstanceLimit(const std::map>& handlers, + s32 vol) override; + bool m_paused{false}; u8 m_group{0}; @@ -90,5 +94,6 @@ class BlockSoundHandler : public SoundHandler { u32 m_next_grain{0}; u32 m_sound_id{0}; + s32 m_start_tick{0}; }; } // namespace snd diff --git a/game/sound/989snd/musicbank.cpp b/game/sound/989snd/musicbank.cpp index bcef3f4f9f..1abe67125c 100644 --- a/game/sound/989snd/musicbank.cpp +++ b/game/sound/989snd/musicbank.cpp @@ -7,12 +7,8 @@ namespace snd { -std::optional> MusicBank::MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) { +std::optional> +MusicBank::MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 tick) { auto& sound = Sounds[sound_id]; // FIXME: global midi list @@ -42,7 +38,8 @@ std::optional> MusicBank::MakeHandler(VoiceManager u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) { + SndPlayParams& params, + s32 tick) { return std::nullopt; } diff --git a/game/sound/989snd/musicbank.h b/game/sound/989snd/musicbank.h index 76a3b75d38..c23f8271e8 100644 --- a/game/sound/989snd/musicbank.h +++ b/game/sound/989snd/musicbank.h @@ -66,17 +66,14 @@ class MusicBank : public SoundBank { std::span samples, std::span midi_data); - std::optional> MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) override; + std::optional> + MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 tick) override; std::optional> MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) override; + SndPlayParams& params, + s32 tick) override; }; } // namespace snd diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index da8c7e6b1f..6655c47a92 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -137,11 +137,19 @@ u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm return 0; } - auto handler = bank->MakeHandler(mVmanager, sound_id, vol, pan, pm, pb); + auto handler = bank->MakeHandler(mVmanager, sound_id, vol, pan, pm, pb, GetTick()); if (!handler.has_value()) { return 0; } + auto handler_to_stop = handler.value()->CheckInstanceLimit(mHandlers, vol); + if (handler_to_stop) { + handler_to_stop->Stop(); + if (handler_to_stop == handler.value().get()) { + return 0; + } + } + u32 handle = mHandleAllocator.GetId(); mHandlers.emplace(handle, std::move(handler.value())); // fmt::print("play_sound {}:{} - {}\n", bank_id, sound_id, handle); @@ -149,6 +157,16 @@ u32 Player::PlaySound(BankHandle bank_id, u32 sound_id, s32 vol, s32 pan, s32 pm return handle; } +void Player::DebugPrintAllSoundsInBank(BankHandle bank_id) { + std::scoped_lock lock(mTickLock); + auto* bank = mLoader.GetBankByHandle(bank_id); + if (!bank) { + lg::error("DebugPrintAllSoundsInBank: invalid bank"); + return; + } + bank->DebugPrintAllSounds(); +} + u32 Player::PlaySoundByName(BankHandle bank_id, char* bank_name, char* sound_name, diff --git a/game/sound/989snd/player.h b/game/sound/989snd/player.h index f03498c20a..41e6771790 100644 --- a/game/sound/989snd/player.h +++ b/game/sound/989snd/player.h @@ -2,10 +2,10 @@ // SPDX-License-Identifier: ISC #pragma once +#include #include #include #include -#include #include #include "ame_handler.h" @@ -42,6 +42,7 @@ class Player { s32 pan, s32 pm, s32 pb); + void DebugPrintAllSoundsInBank(BankHandle bank); void SetSoundReg(u32 sound_id, u8 reg, u8 value); void SetGlobalExcite(u8 value) { GlobalExcite = value; }; bool SoundStillActive(u32 sound_id); @@ -71,7 +72,7 @@ class Player { private: std::recursive_mutex mTickLock; // TODO does not need to recursive with some light restructuring IdAllocator mHandleAllocator; - std::unordered_map> mHandlers; + std::map> mHandlers; void Tick(s16Output* stream, int samples); diff --git a/game/sound/989snd/sfxblock.cpp b/game/sound/989snd/sfxblock.cpp index 9fc9920ec1..07387c0f37 100644 --- a/game/sound/989snd/sfxblock.cpp +++ b/game/sound/989snd/sfxblock.cpp @@ -5,20 +5,24 @@ #include "common/log/log.h" +#include "third-party/magic_enum.hpp" + namespace snd { std::optional> SFXBlock::MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) { + SndPlayParams& params, + s32 current_tick) { auto& SFX = Sounds[sound_id]; if (SFX.Grains.empty()) { return std::nullopt; } - auto handler = std::make_unique(*this, SFX, vm, vol, pan, params, sound_id); + auto handler = + std::make_unique(*this, SFX, vm, vol, pan, params, sound_id, current_tick); return handler; } @@ -31,4 +35,22 @@ std::optional SFXBlock::GetSoundByName(const char* name) { return std::nullopt; } +void SFXBlock::DebugPrintAllSounds() { + for (const auto& [name, id] : Names) { + printf("%s : %d\n", name.c_str(), id); + const auto& sound = Sounds.at(id); + printf(" Vol: %d\n", sound.Vol); + printf(" VolGroup: %d\n", sound.VolGroup); + printf(" Pan: %d\n", sound.Pan); + printf(" InstanceLimit: %d\n", sound.InstanceLimit); + printf(" Flags: 0x%x\n", sound.Flags.flags); + printf(" User: 0x%x 0x%x 0x%x 0x%x\n", sound.UserData.data[0], sound.UserData.data[1], + sound.UserData.data[2], sound.UserData.data[3]); + printf(" Grains\n"); + for (const auto& grain : sound.Grains) { + fmt::print(" {} ({})\n", magic_enum::enum_name(grain.Type), (int)grain.Type); + } + } +} + } // namespace snd diff --git a/game/sound/989snd/sfxblock.h b/game/sound/989snd/sfxblock.h index e3a91097c5..4e779112e7 100644 --- a/game/sound/989snd/sfxblock.h +++ b/game/sound/989snd/sfxblock.h @@ -52,13 +52,15 @@ class SFXBlock : public SoundBank { u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) override; + SndPlayParams& params, + s32 current_tick) override; std::optional GetName() override { return Name; }; std::optional GetSoundByName(const char* name) override; std::optional GetSoundUserData(u32 sound_id) override { return &Sounds.at(sound_id).UserData; }; + void DebugPrintAllSounds() override; }; } // namespace snd diff --git a/game/sound/989snd/sfxgrain.cpp b/game/sound/989snd/sfxgrain.cpp index 835e677ff5..6bf63735fe 100644 --- a/game/sound/989snd/sfxgrain.cpp +++ b/game/sound/989snd/sfxgrain.cpp @@ -158,7 +158,8 @@ s32 Grain::snd_SFX_GRAIN_TYPE_STARTCHILDSOUND(BlockSoundHandler& handler) { s32 index = psp.sound_id; if (index >= 0) { - auto child_handler = block.MakeHandler(handler.m_vm, index, vol, pan, params); + auto child_handler = + block.MakeHandler(handler.m_vm, index, vol, pan, params, handler.m_start_tick); if (child_handler.has_value()) { handler.m_children.emplace_front(std::move(child_handler.value())); } @@ -257,7 +258,7 @@ s32 Grain::snd_SFX_GRAIN_TYPE_RAND_PLAY(BlockSoundHandler& handler) { auto cp = std::get(data); auto options = cp.param[0]; auto count = cp.param[1]; - auto previous = cp.param[2]; + auto& previous = cp.param[2]; int rnd = rand() % options; if (rnd == previous) { diff --git a/game/sound/989snd/sndplay.cpp b/game/sound/989snd/sndplay.cpp index cf10838738..ba934983f5 100644 --- a/game/sound/989snd/sndplay.cpp +++ b/game/sound/989snd/sndplay.cpp @@ -28,6 +28,7 @@ int main(int argc, char* argv[]) { printf("commands:\n"); printf(" play [id]\n"); printf(" stop\n"); + printf(" dump-info\n"); while (true) { printf("> "); @@ -77,6 +78,10 @@ int main(int argc, char* argv[]) { printf("stopping all sounds\n"); player.StopAllSounds(); } + + if (parts[0] == "dump-info") { + player.DebugPrintAllSoundsInBank(bankid); + } } return 0; diff --git a/game/sound/989snd/sound_handler.h b/game/sound/989snd/sound_handler.h index 85ef182134..c7fe71596a 100644 --- a/game/sound/989snd/sound_handler.h +++ b/game/sound/989snd/sound_handler.h @@ -2,6 +2,9 @@ // SPDX-License-Identifier: ISC #pragma once +#include +#include + #include "common/common_types.h" namespace snd { @@ -25,5 +28,13 @@ class SoundHandler { virtual void SetPBend(s32 /*mod*/){}; virtual void SetRegister(u8 /*reg*/, u8 /*value*/) {} virtual u32 SoundID() const { return -1; } + + // Check if this handler violates an instance limit. If so, return pointer to the sound that + // should be removed. + virtual SoundHandler* CheckInstanceLimit( + const std::map>& handlers, + s32 vol) { + return nullptr; + } }; } // namespace snd diff --git a/game/sound/989snd/soundbank.h b/game/sound/989snd/soundbank.h index 3b20e50446..dac216b934 100644 --- a/game/sound/989snd/soundbank.h +++ b/game/sound/989snd/soundbank.h @@ -54,32 +54,31 @@ class SoundBank { u32 BankID; s8 BankNum; - virtual std::optional> MakeHandler(VoiceManager& vm, - u32 sound_id, - s32 vol, - s32 pan, - s32 pm, - s32 pb) { + virtual std::optional> + MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, s32 pm, s32 pb, s32 current_tick) { SndPlayParams params{}; params.vol = vol; params.pan = pan; params.pitch_mod = pm; params.pitch_bend = pb; - return MakeHandler(vm, sound_id, -1, -1, params); + return MakeHandler(vm, sound_id, -1, -1, params, current_tick); }; virtual std::optional> MakeHandler(VoiceManager& vm, u32 sound_id, s32 vol, s32 pan, - SndPlayParams& params) = 0; + SndPlayParams& params, + s32 current_tick) = 0; virtual std::optional GetName() { return std::nullopt; }; virtual std::optional GetSoundByName(const char* /*name*/) { return std::nullopt; }; virtual std::optional GetSoundUserData(u32 /*sound_id*/) { return std::nullopt; }; + + virtual void DebugPrintAllSounds() {} }; } // namespace snd diff --git a/goal_src/jak1/engine/draw/drawable.gc b/goal_src/jak1/engine/draw/drawable.gc index ff2f1ac747..f55589fb05 100644 --- a/goal_src/jak1/engine/draw/drawable.gc +++ b/goal_src/jak1/engine/draw/drawable.gc @@ -650,6 +650,24 @@ 0 (none)) +;; og:preserve-this added +(defun dma-add-process-drawable-hud-merc ((pd process-drawable) (dc draw-control) (arg2 symbol) (buf dma-buffer)) + (logclear! (-> dc status) (draw-status was-drawn)) + (when (not (logtest? (-> dc status) (draw-status hidden no-anim no-skeleton-update))) + (let ((vu-lights (scratchpad-object vu-lights :offset 64)) + (hud-lights *hud-lights*)) + (vector-copy! (-> vu-lights direction 0) (-> hud-lights direction 0)) + (vector-copy! (-> vu-lights direction 1) (-> hud-lights direction 1)) + (vector-copy! (-> vu-lights direction 2) (-> hud-lights direction 2)) + (vector-copy! (-> vu-lights color 0) (-> hud-lights color 0)) + (vector-copy! (-> vu-lights color 1) (-> hud-lights color 1)) + (vector-copy! (-> vu-lights color 2) (-> hud-lights color 2)) + (vector-copy! (-> vu-lights ambient) (-> hud-lights ambient))) + (lod-set! dc 0) + (logior! (-> dc status) (draw-status was-drawn)) + (draw-bones-hud-merc dc buf)) + (none)) + (defun add-process-drawable ((arg0 process-drawable) (arg1 draw-control) (arg2 symbol) (arg3 dma-buffer)) ((-> arg1 dma-add-func) arg0 arg1 arg2 arg3) (none)) diff --git a/goal_src/jak1/engine/gfx/foreground/bones.gc b/goal_src/jak1/engine/gfx/foreground/bones.gc index f16cd1bbcb..e2342357ce 100644 --- a/goal_src/jak1/engine/gfx/foreground/bones.gc +++ b/goal_src/jak1/engine/gfx/foreground/bones.gc @@ -1506,3 +1506,104 @@ (none)) (define-extern draw-bones-hud (function draw-control dma-buffer none)) + +;; og:preserve-this added +(defun draw-bones-hud-merc ((draw draw-control) (dma-buf dma-buffer)) + (local-vars (at-2 int) (t2-10 vu-lights) (t3-3 uint128) (t3-4 uint128) (t3-5 uint128) (t3-6 uint128)) + (let ((buf dma-buf)) + (let* ((dma-start (-> buf base)) + (joints (+ (-> draw mgeo num-joints) 3)) + (bone-calc (the bone-calculation (&+ dma-start 16))) + (bone-calc-size (* joints 128)) + (mat-data (the object (&+ dma-start 64))) + (spr-work (scratchpad-object terrain-context))) + ;; align the matrix data to 64 bytes (we know it is at least 16 byte aligned) + (let ((mat-aligned (logand (the int mat-data) 48))) + (when (nonzero? mat-aligned) + (set! mat-data (&- (&+ (the pointer mat-data) 64) (the uint mat-aligned))))) + (let ((regs (scratchpad-object bone-regs :offset 240))) + (set! (-> regs joint-ptr) (the (inline-array joint) (-> draw jgeo data 0))) + (set! (-> regs bone-ptr) (-> draw skeleton bones)) + (set! (-> regs num-bones) joints)) + (let ((t2-0 mat-data) + (bone-mem (scratchpad-object bone-memory :offset 16)) + (bone-list *bone-calculation-list*) + (t1-6 bone-calc)) + (let ((t4-0 (-> bone-mem work regs joint-ptr)) + (t5-0 (-> bone-mem work regs bone-ptr)) + (t6-1 (-> bone-mem work regs num-bones)) + (t7-0 t1-6)) + (set! (-> t7-0 flags) (bone-calc-flags bncfl01)) + (set! (-> t7-0 num-bones) t6-1) + (set! (-> t7-0 matrix-area) (the (inline-array matrix) t2-0)) + (set! (-> t7-0 joints) t4-0) + (set! (-> t7-0 bones) t5-0) + (set! (-> t7-0 next) (the bone-calculation 0))) + (if (nonzero? (-> bone-list next)) (set! (-> bone-list next next) t1-6)) + (if (zero? (-> bone-list first)) (set! (-> bone-list first) t1-6)) + (set! (-> bone-list next) t1-6)) + (&+ (the pointer bone-calc) 48) + (let ((a2-2 (the object (+ (the uint mat-data) bone-calc-size)))) + (set! (-> (the (pointer uint128) dma-start)) + (logior (-> spr-work work foreground bone-mem work next-tag quad) (shl (the-as int a2-2) 32))) + (when (= (-> draw data-format) 1) + (let ((mgeo (-> draw lod-set lod 0 geo))) + (dotimes (effect-idx (the-as int (-> mgeo header effect-count))) + (cond + ((nonzero? (-> mgeo effect effect-idx envmap-usage)) + (let* ((t1-7 (-> *merc-bucket-info* light)) + (lights (scratchpad-object vu-lights :offset 64)) + (t0-10 7) + (t1-8 (the-as object t1-7))) + (b! (< (+ t0-10 -4) 0) cfg-9 :delay (set! t2-10 lights)) + (nop!) + (label cfg-8) + (let ((t6-2 (-> t2-10 direction 0 quad))) + (nop!) + (let ((t3-2 (-> t2-10 direction 1 quad))) + (+! t0-10 -4) + (let ((t4-1 (-> t2-10 direction 2 quad))) + (set! t1-8 (&+ (the pointer t1-8) 64)) + (let ((t5-1 (-> t2-10 color 0 quad))) + (set! t2-10 (the vu-lights (-> t2-10 color 1))) + (store-qw (&+ (the pointer t1-8) -64) t6-2) + (let ((t6-3 (+ t0-10 -4))) + (store-qw (&+ (the pointer t1-8) -48) t3-2) + (nop!) + (store-qw (&+ (the-as pointer t1-8) -32) t4-1) + (b! (>= t6-3 0) cfg-8 :delay (store-qw (&+ (the-as pointer t1-8) -16) t5-1))))))) + (label cfg-9) + (b! (zero? t0-10) cfg-14 :delay (set! t3-3 (-> t2-10 direction 0 quad))) + (let ((t2-11 (-> t2-10 direction 1)) + (t1-9 (-> (the vu-lights t1-8) direction 1)) + (t0-11 (+ t0-10 -1))) + (store-qw (&+ t1-9 -16) t3-3) + (b! (zero? t0-11) cfg-14 :delay (set! t3-4 (-> t2-11 quad))) + (let ((t2-12 (&+ t2-11 16)) + (t1-10 (&+ t1-9 16)) + (t0-12 (+ t0-11 -1))) + (store-qw (&+ t1-10 -16) t3-4) + (b! (zero? t0-12) cfg-14 :delay (set! t3-5 (-> t2-12 quad))) + (let ((t2-13 (&+ t2-12 16)) + (t1-11 (&+ t1-10 16)) + (t0-13 (+ t0-12 -1))) + (store-qw (&+ t1-11 -16) t3-5) + (b! (zero? t0-13) cfg-14 :delay (set! t3-6 (-> t2-13 quad))) + (&+ t2-13 16) + (let ((t1-12 (&+ t1-11 16))) (+ t0-13 -1) (store-qw (&+ t1-12 -16) t3-6)))))) + (label cfg-14) + (set! (-> *merc-bucket-info* effect effect-idx color-fade) (new 'static 'rgba :r #x80 :g #x80 :b #x80 :a #x80)) + (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 0)) + (set! (-> *merc-bucket-info* effect effect-idx ignore-alpha) (the-as uint 1))) + (else (set! (-> *merc-bucket-info* effect effect-idx use-mercneric) (the-as uint 1)))))) + (when (logtest? *vu1-enable-user* (vu1-renderer-mask merc)) + (set! (-> buf base) (pc-merc-draw-request draw (the pointer a2-2) (the pointer mat-data) #f (the (pointer float) 0))) + (set! a2-2 (-> buf base)))) + (let ((a0-17 (logand (the int a2-2) 48))) + (b! (zero? a0-17) cfg-22 :delay (set! at-2 #x20000000)) + (set! (-> (the (pointer int128) a2-2)) (the int128 at-2)) + (let ((v1-2 (the pointer a2-2))) + (set! a2-2 (+ (&- (the pointer a2-2) (the uint a0-17)) 64)) + (store-u32 (&+ v1-2 4) (the int a2-2)))) + (label cfg-22) + (set! (-> buf base) (the pointer a2-2)))))) diff --git a/goal_src/jak3/engine/common-obs/cloth-art-h.gc b/goal_src/jak3/engine/common-obs/cloth-art-h.gc index 0527fa11e5..f0f982b114 100644 --- a/goal_src/jak3/engine/common-obs/cloth-art-h.gc +++ b/goal_src/jak3/engine/common-obs/cloth-art-h.gc @@ -135,6 +135,9 @@ (defmacro static-cloth-params (ag-name args) `(let ((parms (new 'static 'cloth-params))) + (false! (-> parms alt-tex-name)) + (false! (-> parms alt-tex-name2)) + (false! (-> parms alt-tex-name3)) ,@(apply (lambda (x) (if (and (eq? (car x) 'mesh) (not (integer? (cadr x)))) `(set! (-> parms ,(car x)) ,(art-elt-index ag-name (cadr x))) `(set! (-> parms ,(car x)) ,(cadr x)) diff --git a/goalc/build_actor/common/MercExtract.cpp b/goalc/build_actor/common/MercExtract.cpp index db20ea6951..9d90ab0ecd 100644 --- a/goalc/build_actor/common/MercExtract.cpp +++ b/goalc/build_actor/common/MercExtract.cpp @@ -17,7 +17,6 @@ void extract(const std::string& name, std::map draw_by_material; int mesh_count = 0; int prim_count = 0; - bool has_envmaps = false; int joints = 3; auto skin_idx = find_single_skin(model, all_nodes); if (skin_idx) { @@ -92,12 +91,12 @@ void extract(const std::string& name, tfrag3::MercEffect e; tfrag3::MercEffect envmap_eff; + envmap_eff.has_envmap = false; out.new_model.name = name; out.new_model.max_bones = joints; out.new_model.max_draws = 0; auto process_normal_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { - const auto& mat = model.materials[mat_idx]; eff.all_draws.push_back(d_); auto& draw = eff.all_draws.back(); draw.mode = gltf_util::make_default_draw_mode(); @@ -107,6 +106,8 @@ void extract(const std::string& name, draw.tree_tex_id = 0; return; } + const auto& mat = model.materials[mat_idx]; + int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; if (tex_idx == -1) { lg::warn("Material {} has no texture, using default texture.", mat.name); @@ -172,12 +173,10 @@ void extract(const std::string& name, }; for (const auto& [mat_idx, d_] : draw_by_material) { - const auto& mat = model.materials[mat_idx]; - if (!gltf_util::material_has_envmap(mat)) { + if (mat_idx < 0 || !gltf_util::material_has_envmap(model.materials[mat_idx]) || + !gltf_util::envmap_is_valid(model.materials[mat_idx])) { process_normal_draw(e, mat_idx, d_); } else { - gltf_util::envmap_is_valid(mat, false); - has_envmaps = true; envmap_eff.has_envmap = true; process_envmap_draw(envmap_eff, mat_idx, d_); } @@ -187,7 +186,7 @@ void extract(const std::string& name, if (!e.all_draws.empty()) { out.new_model.effects.push_back(e); } - if (has_envmaps) { + if (envmap_eff.has_envmap) { out.new_model.effects.push_back(envmap_eff); } diff --git a/goalc/build_actor/common/animation_processing.cpp b/goalc/build_actor/common/animation_processing.cpp index 8f02068932..55e75d8b6c 100644 --- a/goalc/build_actor/common/animation_processing.cpp +++ b/goalc/build_actor/common/animation_processing.cpp @@ -21,7 +21,8 @@ int find_max_joint(const tinygltf::Animation& anim, const std::map& no template std::vector compute_keyframes(const std::vector& times, const std::vector& values, - float framerate) { + float framerate, + bool quaternion_interp) { std::vector ret; ASSERT(!times.empty()); ASSERT(times.size() == values.size()); @@ -36,8 +37,15 @@ std::vector compute_keyframes(const std::vector& times, } const float fraction = (t - times.at(i)) / (times.at(i + 1) - times.at(i)); - ret.push_back(values.at(i) * (1.f - fraction) + values.at(i + 1) * fraction); - // lg::info("{} + {:.3f}, {}", i, fraction, ret.back().to_string_aligned()); + if (quaternion_interp) { + float multiplier = 1; + if (values.at(i).dot(values.at(i + 1)) < 0) { + multiplier = -1; + } + ret.push_back(values.at(i) * (1.f - fraction) + values.at(i + 1) * fraction * multiplier); + } else { + ret.push_back(values.at(i) * (1.f - fraction) + values.at(i + 1) * fraction); + } t += 1.f / framerate; } return ret; @@ -47,12 +55,13 @@ template std::vector> extract_keyframed_gltf_vecn( const tinygltf::Model& model, const tinygltf::AnimationSampler& sampler, - float framerate) { + float framerate, + bool quaternion_interp) { std::vector times = gltf_util::extract_floats(model, sampler.input); std::vector> values = gltf_util::extract_vec(model, sampler.output, TINYGLTF_COMPONENT_TYPE_FLOAT); ASSERT(times.size() == values.size()); - return compute_keyframes(times, values, framerate); + return compute_keyframes(times, values, framerate, quaternion_interp); } } // namespace @@ -75,13 +84,13 @@ UncompressedJointAnim extract_anim_from_gltf(const tinygltf::Model& model, const auto& sampler = anim.samplers.at(channel.sampler); if (channel.target_path == "translation") { out.joints.at(channel_joint).trans_frames = - extract_keyframed_gltf_vecn<3>(model, sampler, framerate); + extract_keyframed_gltf_vecn<3>(model, sampler, framerate, false); } else if (channel.target_path == "rotation") { out.joints.at(channel_joint).quat_frames = - extract_keyframed_gltf_vecn<4>(model, sampler, framerate); + extract_keyframed_gltf_vecn<4>(model, sampler, framerate, true); } else if (channel.target_path == "scale") { out.joints.at(channel_joint).scale_frames = - extract_keyframed_gltf_vecn<3>(model, sampler, framerate); + extract_keyframed_gltf_vecn<3>(model, sampler, framerate, false); } else { lg::die("unknown target_path {}", channel.target_path); } diff --git a/goalc/build_level/jak1/build_level.cpp b/goalc/build_level/jak1/build_level.cpp index 810df3e7c7..8395940fbb 100644 --- a/goalc/build_level/jak1/build_level.cpp +++ b/goalc/build_level/jak1/build_level.cpp @@ -234,8 +234,9 @@ bool run_build_level(const std::string& input_file, if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { const auto& ag_file = db.lookup_record(ag); lg::info("custom level: extracting art group {}", ag_file.name_in_dgo); + decompiler::MercSwapInfo info; decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, - db.version()); + db.version(), info); } } } diff --git a/goalc/build_level/jak2/build_level.cpp b/goalc/build_level/jak2/build_level.cpp index 7ab7db5bd7..ec0f6f9ae3 100644 --- a/goalc/build_level/jak2/build_level.cpp +++ b/goalc/build_level/jak2/build_level.cpp @@ -150,8 +150,9 @@ bool run_build_level(const std::string& input_file, if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { const auto& ag_file = db.lookup_record(ag); lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); + decompiler::MercSwapInfo info; decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, - db.version()); + db.version(), info); } } } diff --git a/goalc/build_level/jak3/build_level.cpp b/goalc/build_level/jak3/build_level.cpp index 57185b736b..fd997cfa68 100644 --- a/goalc/build_level/jak3/build_level.cpp +++ b/goalc/build_level/jak3/build_level.cpp @@ -148,8 +148,9 @@ bool run_build_level(const std::string& input_file, if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { const auto& ag_file = db.lookup_record(ag); lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); + decompiler::MercSwapInfo info; decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, - db.version()); + db.version(), info); } } }