From bd1dde8774dd7a6e0c2679c4471b0504d2182c53 Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sat, 19 Jul 2025 03:44:52 -0400 Subject: [PATCH 1/6] Implement optional dependencies for mods and add `recomp_get_mod_file_path` export (#118) --- N64Recomp | 2 +- librecomp/include/librecomp/mods.hpp | 23 ++++++ librecomp/src/mod_config_api.cpp | 14 ++++ librecomp/src/mod_manifest.cpp | 21 +++++ librecomp/src/mods.cpp | 119 +++++++++++++++++++++------ librecomp/src/recomp.cpp | 20 +++++ 6 files changed, 172 insertions(+), 27 deletions(-) diff --git a/N64Recomp b/N64Recomp index 8781eb4..6e7a5bd 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit 8781eb44acbf55cb6a109d2aa5529aadb95a419d +Subproject commit 6e7a5bdb2f1fff8b3d16122cc0b760d3c2541eae diff --git a/librecomp/include/librecomp/mods.hpp b/librecomp/include/librecomp/mods.hpp index 5f1020a..3393f65 100644 --- a/librecomp/include/librecomp/mods.hpp +++ b/librecomp/include/librecomp/mods.hpp @@ -96,6 +96,7 @@ namespace recomp { }; std::string error_to_string(ModLoadError); + void unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg); enum class CodeModLoadError { Good, @@ -133,6 +134,19 @@ namespace recomp { String }; + enum class DependencyStatus { + // Do not change these values as they're exposed in the mod API! + + // The dependency was found and the version requirement was met. + Found = 0, + // The ID given is not a dependency of the mod in question. + InvalidDependency = 1, + // The dependency was not found. + NotFound = 2, + // The dependency was found, but the version requirement was not met. + WrongVersion = 3 + }; + struct ModFileHandle { virtual ~ModFileHandle() = default; virtual std::vector read_file(const std::string& filepath, bool& exists) const = 0; @@ -171,6 +185,7 @@ namespace recomp { struct Dependency { std::string mod_id; Version version; + bool optional; }; struct ConfigOptionEnum { @@ -363,6 +378,10 @@ namespace recomp { bool register_container_type(const std::string& extension, const std::vector& content_types, bool requires_manifest); ModContentTypeId get_code_content_type() const { return code_content_type_id; } bool is_content_runtime_toggleable(ModContentTypeId content_type) const; + std::string get_mod_display_name(size_t mod_index) const; + std::filesystem::path get_mod_path(size_t mod_index) const; + std::pair get_mod_import_info(size_t mod_index, size_t import_index) const; + DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id) const; private: ModOpenError open_mod_from_manifest(ModManifest &manifest, std::string &error_param, const std::vector &supported_content_types, bool requires_manifest); ModOpenError open_mod_from_path(const std::filesystem::path& mod_path, std::string& error_param, const std::vector& supported_content_types, bool requires_manifest); @@ -621,6 +640,10 @@ namespace recomp { size_t get_mod_order_index(size_t mod_index); ModContentTypeId register_mod_content_type(const ModContentType& type); bool register_mod_container_type(const std::string& extension, const std::vector& content_types, bool requires_manifest); + std::string get_mod_display_name(size_t mod_index); + std::filesystem::path get_mod_path(size_t mod_index); + std::pair get_mod_import_info(size_t mod_index, size_t import_index); + DependencyStatus is_dependency_met(size_t mod_index, const std::string& dependency_id); void register_config_exports(); } diff --git a/librecomp/src/mod_config_api.cpp b/librecomp/src/mod_config_api.cpp index 14712a1..73e9169 100644 --- a/librecomp/src/mod_config_api.cpp +++ b/librecomp/src/mod_config_api.cpp @@ -96,6 +96,18 @@ void recomp_get_mod_folder_path(uint8_t* rdram, recomp_context* ctx) { return_string(rdram, ctx, std::filesystem::absolute(mod_folder_path).u8string()); } +void recomp_get_mod_file_path(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { + std::filesystem::path mod_file_path = recomp::mods::get_mod_path(mod_index); + + return_string(rdram, ctx, std::filesystem::absolute(mod_file_path).u8string()); +} + +void recomp_is_dependency_met(uint8_t* rdram, recomp_context* ctx, size_t mod_index) { + std::string dependency_id = _arg_string<0>(rdram, ctx); + recomp::mods::DependencyStatus status = recomp::mods::is_dependency_met(mod_index, dependency_id); + _return(ctx, static_cast(status)); +} + void recomp::mods::register_config_exports() { recomp::overlays::register_ext_base_export("recomp_get_config_u32", recomp_get_config_u32); recomp::overlays::register_ext_base_export("recomp_get_config_double", recomp_get_config_double); @@ -105,4 +117,6 @@ void recomp::mods::register_config_exports() { recomp::overlays::register_ext_base_export("recomp_change_save_file", recomp_change_save_file); recomp::overlays::register_base_export("recomp_get_save_file_path", recomp_get_save_file_path); recomp::overlays::register_base_export("recomp_get_mod_folder_path", recomp_get_mod_folder_path); + recomp::overlays::register_ext_base_export("recomp_get_mod_file_path", recomp_get_mod_file_path); + recomp::overlays::register_ext_base_export("recomp_is_dependency_met", recomp_is_dependency_met); } diff --git a/librecomp/src/mod_manifest.cpp b/librecomp/src/mod_manifest.cpp index 4bccf00..b41ec12 100644 --- a/librecomp/src/mod_manifest.cpp +++ b/librecomp/src/mod_manifest.cpp @@ -181,6 +181,7 @@ const std::string authors_key = "authors"; const std::string minimum_recomp_version_key = "minimum_recomp_version"; const std::string enabled_by_default_key = "enabled_by_default"; const std::string dependencies_key = "dependencies"; +const std::string optional_dependencies_key = "optional_dependencies"; const std::string native_libraries_key = "native_libraries"; const std::string config_schema_key = "config_schema"; @@ -602,6 +603,26 @@ recomp::mods::ModOpenError recomp::mods::parse_manifest(ModManifest& ret, const error_param = dep_string; return ModOpenError::InvalidDependencyString; } + cur_dep.optional = false; + + size_t dependency_index = ret.dependencies.size(); + ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index); + ret.dependencies.emplace_back(std::move(cur_dep)); + } + + // Optional dependencies (optional) + std::vector optional_dep_strings{}; + current_error = try_get_vec(optional_dep_strings, manifest_json, optional_dependencies_key, false, error_param); + if (current_error != ModOpenError::Good) { + return current_error; + } + for (const std::string& dep_string : optional_dep_strings) { + Dependency cur_dep; + if (!parse_dependency(dep_string, cur_dep)) { + error_param = dep_string; + return ModOpenError::InvalidDependencyString; + } + cur_dep.optional = true; size_t dependency_index = ret.dependencies.size(); ret.dependencies_by_id.emplace(cur_dep.mod_id, dependency_index); diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 36b1373..7d9404f 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -975,6 +975,45 @@ bool recomp::mods::ModContext::register_container_type(const std::string& extens return true; } +std::string recomp::mods::ModContext::get_mod_display_name(size_t mod_index) const { + return opened_mods[mod_index].manifest.display_name; +} + +std::filesystem::path recomp::mods::ModContext::get_mod_path(size_t mod_index) const { + return opened_mods[mod_index].manifest.mod_root_path; +} + +std::pair recomp::mods::ModContext::get_mod_import_info(size_t mod_index, size_t import_index) const { + const ModHandle& mod = opened_mods[mod_index]; + const N64Recomp::ImportSymbol& imported_func = mod.recompiler_context->import_symbols[import_index]; + const std::string& dependency_id = mod.recompiler_context->dependencies[imported_func.dependency_index]; + + return std::make_pair(std::string{ dependency_id }, std::string{ imported_func.base.name }); +} + +recomp::mods::DependencyStatus recomp::mods::ModContext::is_dependency_met(size_t mod_index, const std::string& dependency_id) const { + const ModHandle& mod = opened_mods[mod_index]; + + auto find_dep = mod.manifest.dependencies_by_id.find(dependency_id); + if (find_dep == mod.manifest.dependencies_by_id.end()) { + return DependencyStatus::InvalidDependency; + } + + auto find_dep_mod = loaded_mods_by_id.find(dependency_id); + if (find_dep_mod == loaded_mods_by_id.end()) { + return DependencyStatus::NotFound; + } + + const Dependency& dep = mod.manifest.dependencies[find_dep->second]; + const ModHandle& dep_mod = opened_mods[find_dep_mod->second]; + + if (dep_mod.manifest.version < dep.version) { + return DependencyStatus::WrongVersion; + } + + return DependencyStatus::Found; +} + bool recomp::mods::ModContext::is_content_runtime_toggleable(ModContentTypeId content_type) const { assert(content_type.value < content_types.size()); @@ -1026,7 +1065,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable if (mod_from_stack_it != opened_mods_by_id.end()) { const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second]; for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) { - if (!auto_enabled_mods.contains(dependency.mod_id)) { + if (!dependency.optional && !auto_enabled_mods.contains(dependency.mod_id)) { auto_enabled_mods.emplace(dependency.mod_id); mod_stack.emplace_back(dependency.mod_id); @@ -1071,7 +1110,7 @@ void recomp::mods::ModContext::enable_mod(const std::string& mod_id, bool enable if (mod_from_stack_it != opened_mods_by_id.end()) { const ModHandle &mod_from_stack_handle = opened_mods[mod_from_stack_it->second]; for (const Dependency &dependency : mod_from_stack_handle.manifest.dependencies) { - if (!new_auto_enabled_mods.contains(dependency.mod_id)) { + if (!dependency.optional && !new_auto_enabled_mods.contains(dependency.mod_id)) { new_auto_enabled_mods.emplace(dependency.mod_id); mod_stack.emplace_back(dependency.mod_id); } @@ -2038,25 +2077,28 @@ void recomp::mods::ModContext::check_dependencies(recomp::mods::ModHandle& mod, mod.disable_runtime_toggle(); } for (const recomp::mods::Dependency& cur_dep : mod.manifest.dependencies) { - // Look for the dependency in the loaded mod mapping. - auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id); - if (find_loaded_dep_it == loaded_mods_by_id.end()) { - errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id); - continue; - } + if (!cur_dep.optional) { + // Look for the dependency in the loaded mod mapping. + auto find_loaded_dep_it = loaded_mods_by_id.find(cur_dep.mod_id); + if (find_loaded_dep_it != loaded_mods_by_id.end()) { + ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second]; + if (cur_dep.version > dep_mod.manifest.version) + { + std::stringstream error_param_stream{}; + error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " << + (int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " << + (int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << ""; + errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str()); + } - ModHandle& dep_mod = opened_mods[find_loaded_dep_it->second]; - if (cur_dep.version > dep_mod.manifest.version) - { - std::stringstream error_param_stream{}; - error_param_stream << "requires mod \"" << cur_dep.mod_id << "\" " << - (int)cur_dep.version.major << "." << (int)cur_dep.version.minor << "." << (int)cur_dep.version.patch << ", got " << - (int)dep_mod.manifest.version.major << "." << (int)dep_mod.manifest.version.minor << "." << (int)dep_mod.manifest.version.patch << ""; - errors.emplace_back(ModLoadError::WrongDependencyVersion, error_param_stream.str()); + // Prevent the dependency from being toggled at runtime, as it's required for this mod. + dep_mod.disable_runtime_toggle(); + } + // Add an error for this mod if the dependency isn't optional. + else { + errors.emplace_back(ModLoadError::MissingDependency, cur_dep.mod_id); + } } - - // Prevent the dependency from being toggled at runtime, as it's required for this mod. - dep_mod.disable_runtime_toggle(); } } @@ -2362,14 +2404,24 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci } else { auto find_mod_it = loaded_mods_by_id.find(dependency_id); - if (find_mod_it == loaded_mods_by_id.end()) { - error_param = "Failed to find import dependency while loading code: " + dependency_id; - // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list - // is validated against the manifest's. - return CodeModLoadError::InternalError; + if (find_mod_it != loaded_mods_by_id.end()) { + const auto& dependency = opened_mods[find_mod_it->second]; + did_find_func = dependency.get_export_function(imported_func.base.name, func_handle); + } + else { + auto find_optional_it = mod.manifest.dependencies_by_id.find(dependency_id); + if (find_optional_it != mod.manifest.dependencies_by_id.end()) { + uintptr_t shim_argument = ((import_index & 0xFFFFFFFFu) << 32) | (mod_index & 0xFFFFFFFFu); + func_handle = shim_functions.emplace_back(std::make_unique(unmet_dependency_handler, shim_argument)).get()->get_func(); + did_find_func = true; + } + else { + error_param = "Failed to find import dependency while loading code: " + dependency_id; + // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list + // is validated against the manifest's. + return CodeModLoadError::InternalError; + } } - const auto& dependency = opened_mods[find_mod_it->second]; - did_find_func = dependency.get_export_function(imported_func.base.name, func_handle); } if (!did_find_func) { @@ -2503,3 +2555,18 @@ void recomp::mods::ModContext::unload_mods() { num_events = recomp::overlays::num_base_events(); active_game = (size_t)-1; } + +void recomp::mods::unmet_dependency_handler(uint8_t* rdram, recomp_context* ctx, uintptr_t arg) { + size_t caller_mod_index = (arg >> 0) & uint64_t(0xFFFFFFFF); + size_t import_index = (arg >> 32) & uint64_t(0xFFFFFFFF); + + std::string mod_name = recomp::mods::get_mod_display_name(caller_mod_index); + std::pair import_info = recomp::mods::get_mod_import_info(caller_mod_index, import_index); + + ultramodern::error_handling::message_box( + ( + "Fatal error in mod \"" + mod_name + "\": Called function \"" + import_info.second + "\" in unmet optional dependency \"" + import_info.first + "\".\n" + ).c_str() + ); + ULTRAMODERN_QUICK_EXIT(); +} diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp index ddf7a02..8a03dbd 100644 --- a/librecomp/src/recomp.cpp +++ b/librecomp/src/recomp.cpp @@ -129,6 +129,26 @@ bool recomp::mods::register_mod_container_type(const std::string& extension, con return mod_context->register_container_type(extension, content_types, requires_manifest); } +std::string recomp::mods::get_mod_display_name(size_t mod_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_display_name(mod_index); +} + +std::filesystem::path recomp::mods::get_mod_path(size_t mod_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_path(mod_index); +} + +std::pair recomp::mods::get_mod_import_info(size_t mod_index, size_t import_index) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->get_mod_import_info(mod_index, import_index); +} + +recomp::mods::DependencyStatus recomp::mods::is_dependency_met(size_t mod_index, const std::string& dependency_id) { + std::lock_guard mod_lock{ mod_context_mutex }; + return mod_context->is_dependency_met(mod_index, dependency_id); +} + bool check_hash(const std::vector& rom_data, uint64_t expected_hash) { uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size()); return calculated_hash == expected_hash; From df547d2c06d058cabb684e373e436f7e3ea13747 Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sat, 19 Jul 2025 04:09:14 -0400 Subject: [PATCH 2/6] Update runtime for fixed address mod sections, fix some live recompiler errors not triggering mod loading errors (#120) --- N64Recomp | 2 +- librecomp/src/mods.cpp | 42 +++++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/N64Recomp b/N64Recomp index 6e7a5bd..c1a6dc9 160000 --- a/N64Recomp +++ b/N64Recomp @@ -1 +1 @@ -Subproject commit 6e7a5bdb2f1fff8b3d16122cc0b760d3c2541eae +Subproject commit c1a6dc93bfa5977de0ea256562058be4f1b73353 diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 7d9404f..58f1c6c 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -505,18 +505,20 @@ recomp::mods::LiveRecompilerCodeHandle::LiveRecompilerCodeHandle( N64Recomp::LiveGenerator generator{ context.functions.size(), recompiler_inputs }; std::vector> dummy_static_funcs{}; + bool errored = false; + for (size_t func_index = 0; func_index < context.functions.size(); func_index++) { std::ostringstream dummy_ostream{}; if (!N64Recomp::recompile_function_live(generator, context, func_index, dummy_ostream, dummy_static_funcs, true)) { - is_good = false; + errored = true; break; } } // Generate the code. recompiler_output = std::make_unique(generator.finish()); - is_good = recompiler_output->good; + is_good = !errored && recompiler_output->good; } void recomp::mods::LiveRecompilerCodeHandle::set_imported_function(size_t import_index, GenericFunction func) { @@ -2167,22 +2169,28 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::init_mod_code(uint8_t* int32_t cur_section_addr = load_address; for (size_t section_index = 0; section_index < mod_sections.size(); section_index++) { const auto& section = mod_sections[section_index]; - for (size_t i = 0; i < section.size; i++) { - MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i]; + // Do not load fixed address sections into mod memory. Use their address as-is. + if (section.fixed_address) { + mod.section_load_addresses[section_index] = section.ram_addr; } - mod.section_load_addresses[section_index] = cur_section_addr; - // Calculate the bss section's address based on the size of this section. - cur_section_addr += section.size; - // Zero the bss section. - for (size_t i = 0; i < section.bss_size; i++) { - MEM_B(i, (gpr)cur_section_addr) = 0; + else { + for (size_t i = 0; i < section.size; i++) { + MEM_B(i, (gpr)cur_section_addr) = binary_data[section.rom_addr + i]; + } + mod.section_load_addresses[section_index] = cur_section_addr; + // Calculate the bss section's address based on the size of this section. + cur_section_addr += section.size; + // Zero the bss section. + for (size_t i = 0; i < section.bss_size; i++) { + MEM_B(i, (gpr)cur_section_addr) = 0; + } + // Calculate the next section's address based on the size of the bss section. + cur_section_addr += section.bss_size; + // Align the next section's address to 16 bytes. + cur_section_addr = (cur_section_addr + 15) & ~15; + // Add some empty space between mods to act as a buffer for misbehaving mods that have out of bounds accesses. + cur_section_addr += 0x400; } - // Calculate the next section's address based on the size of the bss section. - cur_section_addr += section.bss_size; - // Align the next section's address to 16 bytes. - cur_section_addr = (cur_section_addr + 15) & ~15; - // Add some empty space between mods to act as a buffer for misbehaving mods that have out of bounds accesses. - cur_section_addr += 0x400; } // Iterate over each section again after loading them to perform R_MIPS_32 relocations. @@ -2245,7 +2253,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::load_mod_code(uint8_t* std::unordered_map entry_func_hooks{}; std::unordered_map return_func_hooks{}; - // Scan the replacements and check for any + // Scan the replacements to handle hooks on the replaced functions. for (const auto& replacement : mod.recompiler_context->replacements) { // Check if there's a hook slot for the entry of this function. HookDefinition entry_def { From 83891b4231b7524a89f8cc9464347e97d04ee1ba Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Wed, 23 Jul 2025 00:08:44 -0400 Subject: [PATCH 3/6] Fix handling of mod callbacks for events in optional dependencies (#121) --- librecomp/src/mods.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/librecomp/src/mods.cpp b/librecomp/src/mods.cpp index 58f1c6c..13bed97 100644 --- a/librecomp/src/mods.cpp +++ b/librecomp/src/mods.cpp @@ -2447,6 +2447,7 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci GenericFunction func = mod.code_handle->get_function_handle(callback.function_index); size_t event_index = 0; bool did_find_event = false; + bool optional = false; if (dependency_id == N64Recomp::DependencyBaseRecomp) { event_index = recomp::overlays::get_base_event_index(dependency_event.event_name); @@ -2458,23 +2459,40 @@ recomp::mods::CodeModLoadError recomp::mods::ModContext::resolve_code_dependenci did_find_event = mod.get_global_event_index(dependency_event.event_name, event_index); } else { + // Check if the dependency is optional. auto find_mod_it = loaded_mods_by_id.find(dependency_id); if (find_mod_it == loaded_mods_by_id.end()) { - error_param = "Failed to find callback dependency while loading code: " + dependency_id; - // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list - // is validated against the manifest's. - return CodeModLoadError::InternalError; + // Get the manifest's version of the dependency. + auto find_manifest_dep = mod.manifest.dependencies_by_id.find(dependency_id); + // This should always be found, but just in case validate that the find was successful. + // This will get treated as an error if it wasn't found in the manifest. + if (find_manifest_dep != mod.manifest.dependencies_by_id.end()) { + const auto& manifest_dep = mod.manifest.dependencies[find_manifest_dep->second]; + if (manifest_dep.optional) { + optional = true; + } + } + + if (!optional) { + error_param = "Failed to find callback dependency while loading code: " + dependency_id; + // This should never happen, as dependencies are scanned before mod code is loaded and the symbol dependency list + // is validated against the manifest's. + return CodeModLoadError::InternalError; + } + } + else { + const auto& dependency_mod = opened_mods[find_mod_it->second]; + did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index); } - const auto& dependency_mod = opened_mods[find_mod_it->second]; - did_find_event = dependency_mod.get_global_event_index(dependency_event.event_name, event_index); } - if (!did_find_event) { + if (did_find_event) { + recomp::mods::register_event_callback(event_index, mod_index, func); + } + else if (!optional) { error_param = dependency_id + ":" + dependency_event.event_name; return CodeModLoadError::InvalidCallbackEvent; } - - recomp::mods::register_event_callback(event_index, mod_index, func); } // Register hooks. From cea072b59b8db7e9cd3eb2852ce820992d4ff1ae Mon Sep 17 00:00:00 2001 From: Manuel Alfayate Corchete Date: Thu, 24 Jul 2025 21:00:45 +0200 Subject: [PATCH 4/6] Remove unnecessary x11 header include. (#122) --- ultramodern/include/ultramodern/renderer_context.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ultramodern/include/ultramodern/renderer_context.hpp b/ultramodern/include/ultramodern/renderer_context.hpp index 67ae544..b2da932 100644 --- a/ultramodern/include/ultramodern/renderer_context.hpp +++ b/ultramodern/include/ultramodern/renderer_context.hpp @@ -12,7 +12,6 @@ #elif defined(__ANDROID__) # include "android/native_window.h" #elif defined(__linux__) -# include "X11/Xlib.h" # undef None # undef Status # undef LockMask From 257b5db9d6a38c5f9b96fb67ad4f5abc69809b77 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Sun, 10 Aug 2025 01:00:36 -0400 Subject: [PATCH 5/6] Rewrite VI functionality for higher accuracy --- .../include/ultramodern/renderer_context.hpp | 19 +- ultramodern/src/events.cpp | 310 ++++++++++-------- 2 files changed, 198 insertions(+), 131 deletions(-) diff --git a/ultramodern/include/ultramodern/renderer_context.hpp b/ultramodern/include/ultramodern/renderer_context.hpp index b2da932..43032ea 100644 --- a/ultramodern/include/ultramodern/renderer_context.hpp +++ b/ultramodern/include/ultramodern/renderer_context.hpp @@ -28,6 +28,23 @@ struct SDL_Window; namespace ultramodern { namespace renderer { + struct ViRegs { + unsigned int VI_STATUS_REG; + unsigned int VI_ORIGIN_REG; + unsigned int VI_WIDTH_REG; + unsigned int VI_INTR_REG; + unsigned int VI_V_CURRENT_LINE_REG; + unsigned int VI_TIMING_REG; + unsigned int VI_V_SYNC_REG; + unsigned int VI_H_SYNC_REG; + unsigned int VI_LEAP_REG; + unsigned int VI_H_START_REG; + unsigned int VI_V_START_REG; + unsigned int VI_V_BURST_REG; + unsigned int VI_X_SCALE_REG; + unsigned int VI_Y_SCALE_REG; + }; + ViRegs* get_vi_regs(); #if defined(_WIN32) // Native HWND handle to the target window. @@ -67,7 +84,7 @@ namespace ultramodern { virtual void enable_instant_present() = 0; virtual void send_dl(const OSTask* task) = 0; - virtual void update_screen(uint32_t vi_origin) = 0; + virtual void update_screen() = 0; virtual void shutdown() = 0; virtual uint32_t get_display_framerate() const = 0; virtual float get_resolution_scale() const = 0; diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp index fed835d..7fb890b 100644 --- a/ultramodern/src/events.cpp +++ b/ultramodern/src/events.cpp @@ -27,23 +27,82 @@ struct SpTaskAction { OSTask task; }; -struct SwapBuffersAction { - uint32_t origin; +struct ScreenUpdateAction { }; struct UpdateConfigAction { }; -using Action = std::variant; +using Action = std::variant; + +struct ViState { + const OSViMode* mode; + PTR(void) framebuffer; + PTR(OSMesg) mq; + OSMesg msg; + uint32_t state; + uint32_t control; + int retrace_count = 1; +}; + +#define VI_STATE_BLACK 0x20 +#define VI_STATE_REPEATLINE 0x40 static struct { struct { std::thread thread; - PTR(OSMesgQueue) mq = NULLPTR; - PTR(void) current_buffer = NULLPTR; - PTR(void) next_buffer = NULLPTR; - OSMesg msg = (OSMesg)0; - int retrace_count = 1; + int cur_state; + int field; + ViState states[2]; + ultramodern::renderer::ViRegs regs; + + ViState* get_next_state() { + return &states[cur_state ^ 1]; + } + ViState* get_cur_state() { + return &states[cur_state]; + } + void update_vi() { + ViState* next_state = get_next_state(); + const OSViMode* next_mode = next_state->mode; + const OSViCommonRegs* common_regs = &next_mode->comRegs; + const OSViFieldRegs* field_regs = &next_mode->fldRegs[field]; + PTR(void) framebuffer = osVirtualToPhysical(next_state->framebuffer); + PTR(void) origin = framebuffer + field_regs->origin; + + // Process the VI state flags. + uint32_t hStart = common_regs->hStart; + if (next_state->state & VI_STATE_BLACK) { + hStart = 0; + } + + uint32_t yScale = field_regs->yScale; + if (next_state->state & VI_STATE_REPEATLINE) { + yScale = 0; + origin = framebuffer; + } + + // TODO implement osViFade + + // Update VI registers. + regs.VI_ORIGIN_REG = origin; + regs.VI_WIDTH_REG = common_regs->width; + regs.VI_TIMING_REG = common_regs->burst; + regs.VI_V_SYNC_REG = common_regs->vSync; + regs.VI_H_SYNC_REG = common_regs->hSync; + regs.VI_LEAP_REG = common_regs->leap; + regs.VI_H_START_REG = hStart; + regs.VI_V_START_REG = field_regs->vStart; // TODO implement osViExtendVStart + regs.VI_V_BURST_REG = field_regs->vBurst; + regs.VI_INTR_REG = field_regs->vIntr; + regs.VI_X_SCALE_REG = common_regs->xScale; // TODO implement osViSetXScale + regs.VI_Y_SCALE_REG = yScale; // TODO implement osViSetYScale + regs.VI_STATUS_REG = next_state->control; + + // Swap VI states. + cur_state ^= 1; + *get_next_state() = *get_cur_state(); + } } vi; struct { std::thread gfx_thread; @@ -71,8 +130,11 @@ static struct { moodycamel::ConcurrentQueue deleted_threads{}; } events_context{}; +ultramodern::renderer::ViRegs* ultramodern::renderer::get_vi_regs() { + return &events_context.vi.regs; +} + extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { - OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); std::lock_guard lock{ events_context.message_mutex }; switch (event_id) { @@ -96,9 +158,10 @@ extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) { std::lock_guard lock{ events_context.message_mutex }; - events_context.vi.mq = mq_; - events_context.vi.msg = msg; - events_context.vi.retrace_count = retrace_count; + ViState* next_state = events_context.vi.get_next_state(); + next_state->mq = mq_; + next_state->msg = msg; + next_state->retrace_count = retrace_count; } uint64_t total_vis = 0; @@ -107,7 +170,7 @@ uint64_t total_vis = 0; extern std::atomic_bool exited; extern moodycamel::LightweightSemaphore graphics_shutdown_ready; -void set_dummy_vi(); +void set_dummy_vi(bool odd); void vi_thread_func() { ultramodern::set_native_thread_name("VI Thread"); @@ -116,7 +179,7 @@ void vi_thread_func() { ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical); using namespace std::chrono_literals; - int remaining_retraces = events_context.vi.retrace_count; + int remaining_retraces = 1; while (!exited) { // Determine the next VI time (more accurate than adding 16ms each VI interrupt) @@ -142,32 +205,34 @@ void vi_thread_func() { } total_vis = new_total_vis; - remaining_retraces--; + // If the game hasn't started yet, set a dummy VI mode and origin. + if (!ultramodern::is_game_started()) { + static bool odd = false; + set_dummy_vi(odd); + odd = !odd; + } - { - std::lock_guard lock{ events_context.message_mutex }; + // Update VI registers and swap VI modes. + events_context.vi.update_vi(); + + // Queue a screen update for the graphics thread. + events_context.action_queue.enqueue(ScreenUpdateAction{ }); + + // If the game has started, handle sending VI and AI events. + if (ultramodern::is_game_started()) { + remaining_retraces--; + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + ViState* cur_state = events_context.vi.get_cur_state(); if (remaining_retraces == 0) { - remaining_retraces = events_context.vi.retrace_count; + remaining_retraces = cur_state->retrace_count; - if (ultramodern::is_game_started()) { - if (events_context.vi.mq != NULLPTR) { - if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) { - //printf("Game skipped a VI frame!\n"); - } + if (cur_state->mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM cur_state->mq, cur_state->msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a VI frame!\n"); } } - else { - set_dummy_vi(); - static bool swap = false; - uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset - // Offset by one FB every other frame so RT64 continues drawing - if (swap) { - vi_origin += 0x25800; - } - osViSwapBuffer(rdram, vi_origin); - swap = !swap; - } } if (events_context.ai.mq != NULLPTR) { if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { @@ -298,19 +363,20 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re sp_complete(); ultramodern::measure_input_latency(); - auto renderer_start = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto renderer_start = std::chrono::high_resolution_clock::now(); renderer_context->send_dl(&task_action->task); - auto renderer_end = std::chrono::high_resolution_clock::now(); + [[maybe_unused]] auto renderer_end = std::chrono::high_resolution_clock::now(); dp_complete(); // printf("Renderer ProcessDList time: %d us\n", static_cast(std::chrono::duration_cast(renderer_end - renderer_start).count())); } - else if (const auto* swap_action = std::get_if(&action)) { - events_context.vi.current_buffer = events_context.vi.next_buffer; - renderer_context->update_screen(swap_action->origin); + else if (const auto* screen_update_action = std::get_if(&action)) { + (void)screen_update_action; + renderer_context->update_screen(); display_refresh_rate = renderer_context->get_display_framerate(); resolution_scale = renderer_context->get_resolution_scale(); } else if (const auto* config_action = std::get_if(&action)) { + (void)config_action; auto new_config = ultramodern::renderer::get_graphics_config(); if (renderer_context->update_config(old_config, new_config)) { old_config = new_config; @@ -323,79 +389,6 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re renderer_context->shutdown(); } -extern unsigned int VI_STATUS_REG; -extern unsigned int VI_ORIGIN_REG; -extern unsigned int VI_WIDTH_REG; -extern unsigned int VI_INTR_REG; -extern unsigned int VI_V_CURRENT_LINE_REG; -extern unsigned int VI_TIMING_REG; -extern unsigned int VI_V_SYNC_REG; -extern unsigned int VI_H_SYNC_REG; -extern unsigned int VI_LEAP_REG; -extern unsigned int VI_H_START_REG; -extern unsigned int VI_V_START_REG; -extern unsigned int VI_V_BURST_REG; -extern unsigned int VI_X_SCALE_REG; -extern unsigned int VI_Y_SCALE_REG; - -#define VI_STATE_BLACK 0x20 -#define VI_STATE_REPEATLINE 0x40 - -uint32_t hstart = 0; -uint32_t vi_origin_offset = 320 * sizeof(uint16_t); -static uint16_t vi_state = 0; - -void set_dummy_vi() { - VI_STATUS_REG = 0x311E; - VI_WIDTH_REG = 0x140; - VI_V_SYNC_REG = 0x20D; - VI_H_SYNC_REG = 0xC15; - VI_LEAP_REG = 0x0C150C15; - hstart = 0x006C02EC; - VI_X_SCALE_REG = 0x200; - VI_V_CURRENT_LINE_REG = 0x0; - vi_origin_offset = 0x280; - VI_Y_SCALE_REG = 0x400; - VI_V_START_REG = 0x2501FF; - VI_V_BURST_REG = 0xE0204; - VI_INTR_REG = 0x2; -} - -extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { - VI_H_START_REG = hstart; - if (vi_state & VI_STATE_BLACK) { - VI_H_START_REG = 0; - } - - if (vi_state & VI_STATE_REPEATLINE) { - VI_Y_SCALE_REG = 0; - VI_ORIGIN_REG = osVirtualToPhysical(frameBufPtr); - } - - events_context.vi.next_buffer = frameBufPtr; - events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + vi_origin_offset }); -} - -extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { - OSViMode* mode = TO_PTR(OSViMode, mode_); - VI_STATUS_REG = mode->comRegs.ctrl; - VI_WIDTH_REG = mode->comRegs.width; - // burst - VI_V_SYNC_REG = mode->comRegs.vSync; - VI_H_SYNC_REG = mode->comRegs.hSync; - VI_LEAP_REG = mode->comRegs.leap; - hstart = mode->comRegs.hStart; - VI_X_SCALE_REG = mode->comRegs.xScale; - VI_V_CURRENT_LINE_REG = mode->comRegs.vCurrent; - - // TODO swap these every VI to account for fields changing - vi_origin_offset = mode->fldRegs[0].origin; - VI_Y_SCALE_REG = mode->fldRegs[0].yScale; - VI_V_START_REG = mode->fldRegs[0].vStart; - VI_V_BURST_REG = mode->fldRegs[0].vBurst; - VI_INTR_REG = mode->fldRegs[0].vIntr; -} - #define VI_CTRL_TYPE_16 0x00002 #define VI_CTRL_TYPE_32 0x00003 #define VI_CTRL_GAMMA_DITHER_ON 0x00004 @@ -412,6 +405,54 @@ extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { #define VI_CTRL_PIXEL_ADV_3 0x03000 #define VI_CTRL_DITHER_FILTER_ON 0x10000 +static const OSViMode dummy_mode = []() { + OSViMode ret{}; + + ret.type = 2; + ret.comRegs.ctrl = VI_CTRL_TYPE_16 | VI_CTRL_GAMMA_DITHER_ON | VI_CTRL_GAMMA_ON | VI_CTRL_DIVOT_ON | VI_CTRL_ANTIALIAS_MODE_1 | VI_CTRL_PIXEL_ADV_3; + ret.comRegs.width = 0x140; + ret.comRegs.burst = 0x03E52239; + ret.comRegs.vSync = 0x20D; + ret.comRegs.hSync = 0xC15; + ret.comRegs.leap = 0x0C150C15; + ret.comRegs.hStart = 0x006C02EC; + ret.comRegs.xScale = 0x200; + ret.comRegs.vCurrent = 0x0; + + for (int field = 0; field < 2; field++) { + ret.fldRegs[field].origin = 0x280; + ret.fldRegs[field].yScale = 0x400; + ret.fldRegs[field].vStart = 0x2501FF; + ret.fldRegs[field].vBurst = 0xE0204; + ret.fldRegs[field].vIntr = 0x2; + } + + return ret; +}(); + +void set_dummy_vi(bool odd) { + ViState* next_state = events_context.vi.get_next_state(); + next_state->mode = &dummy_mode; + // Set up a dummy framebuffer. + next_state->framebuffer = 0x80700000; + if (odd) { + next_state->framebuffer += 0x25800; + } +} + +extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { + std::lock_guard lock{ events_context.message_mutex }; + events_context.vi.get_next_state()->framebuffer = frameBufPtr; +} + +extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { + std::lock_guard lock{ events_context.message_mutex }; + OSViMode* mode = TO_PTR(OSViMode, mode_); + ViState* next_state = events_context.vi.get_next_state(); + next_state->mode = mode; + next_state->control = next_state->mode->comRegs.ctrl; +} + #define OS_VI_GAMMA_ON 0x0001 #define OS_VI_GAMMA_OFF 0x0002 #define OS_VI_GAMMA_DITHER_ON 0x0004 @@ -422,54 +463,63 @@ extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { #define OS_VI_DITHER_FILTER_OFF 0x0080 extern "C" void osViSetSpecialFeatures(uint32_t func) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* control_out = &next_state->control; if ((func & OS_VI_GAMMA_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_GAMMA_ON; + *control_out |= VI_CTRL_GAMMA_ON; } if ((func & OS_VI_GAMMA_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_GAMMA_ON; + *control_out &= ~VI_CTRL_GAMMA_ON; } if ((func & OS_VI_GAMMA_DITHER_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_GAMMA_DITHER_ON; + *control_out |= VI_CTRL_GAMMA_DITHER_ON; } if ((func & OS_VI_GAMMA_DITHER_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_GAMMA_DITHER_ON; + *control_out &= ~VI_CTRL_GAMMA_DITHER_ON; } if ((func & OS_VI_DIVOT_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_DIVOT_ON; + *control_out |= VI_CTRL_DIVOT_ON; } if ((func & OS_VI_DIVOT_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_DIVOT_ON; + *control_out &= ~VI_CTRL_DIVOT_ON; } if ((func & OS_VI_DITHER_FILTER_ON) != 0) { - VI_STATUS_REG |= VI_CTRL_DITHER_FILTER_ON; - VI_STATUS_REG &= ~VI_CTRL_ANTIALIAS_MASK; + *control_out |= VI_CTRL_DITHER_FILTER_ON; + *control_out &= ~VI_CTRL_ANTIALIAS_MASK; } if ((func & OS_VI_DITHER_FILTER_OFF) != 0) { - VI_STATUS_REG &= ~VI_CTRL_DITHER_FILTER_ON; - //VI_STATUS_REG |= __osViNext->modep->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK; + *control_out &= ~VI_CTRL_DITHER_FILTER_ON; + *control_out |= next_state->mode->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK; } } extern "C" void osViBlack(uint8_t active) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* state_out = &next_state->state; if (active) { - vi_state |= VI_STATE_BLACK; + *state_out |= VI_STATE_BLACK; } else { - vi_state &= ~VI_STATE_BLACK; + *state_out &= ~VI_STATE_BLACK; } } extern "C" void osViRepeatLine(uint8_t active) { + std::lock_guard lock{ events_context.message_mutex }; + ViState* next_state = events_context.vi.get_next_state(); + uint32_t* state_out = &next_state->state; if (active) { - vi_state |= VI_STATE_REPEATLINE; + *state_out |= VI_STATE_REPEATLINE; } else { - vi_state &= ~VI_STATE_REPEATLINE; + *state_out &= ~VI_STATE_REPEATLINE; } } @@ -486,11 +536,11 @@ extern "C" void osViSetYScale(float scale) { } extern "C" PTR(void) osViGetNextFramebuffer() { - return events_context.vi.next_buffer; + return events_context.vi.get_next_state()->framebuffer; } extern "C" PTR(void) osViGetCurrentFramebuffer() { - return events_context.vi.current_buffer; + return events_context.vi.get_cur_state()->framebuffer; } void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { @@ -523,7 +573,7 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::renderer::WindowHandle wind task_thread_ready.wait(); ultramodern::renderer::SetupResult setup_result = renderer_setup_result.load(); - if (renderer_setup_result != ultramodern::renderer::SetupResult::Success) { + if (setup_result != ultramodern::renderer::SetupResult::Success) { auto show_renderer_error = [](const std::string& msg) { std::string error_msg = "An error has been encountered on startup: " + msg; @@ -531,7 +581,7 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::renderer::WindowHandle wind }; const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date."; - switch (renderer_setup_result) { + switch (setup_result) { case ultramodern::renderer::SetupResult::Success: break; case ultramodern::renderer::SetupResult::DynamicLibrariesNotFound: From d34934aa7eb7a424fb5c0186b7a22d8e5ce130e4 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Mon, 11 Aug 2025 01:28:41 -0400 Subject: [PATCH 6/6] Implement osSetTime and move update screen to before VI update --- librecomp/src/ultra_translation.cpp | 9 +++++++++ ultramodern/include/ultramodern/ultra64.h | 2 ++ ultramodern/src/events.cpp | 19 +++++++++++-------- ultramodern/src/timer.cpp | 12 +++++++++++- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/librecomp/src/ultra_translation.cpp b/librecomp/src/ultra_translation.cpp index 24ea0bd..e0d367e 100644 --- a/librecomp/src/ultra_translation.cpp +++ b/librecomp/src/ultra_translation.cpp @@ -73,12 +73,21 @@ extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) { ctx->r2 = osGetCount(); } +extern "C" void osSetCount_recomp(uint8_t * rdram, recomp_context * ctx) { + osSetCount(ctx->r4); +} + extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) { uint64_t total_count = osGetTime(); ctx->r2 = (int32_t)(total_count >> 32); ctx->r3 = (int32_t)(total_count >> 0); } +extern "C" void osSetTime_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t t = ((uint64_t)(ctx->r4) << 32) | ((ctx->r5) & 0xFFFFFFFFu); + osSetTime(t); +} + extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) { uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu); uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10); diff --git a/ultramodern/include/ultramodern/ultra64.h b/ultramodern/include/ultramodern/ultra64.h index 71a412f..31eb106 100644 --- a/ultramodern/include/ultramodern/ultra64.h +++ b/ultramodern/include/ultramodern/ultra64.h @@ -290,7 +290,9 @@ void osViSetYScale(float scale); PTR(void) osViGetNextFramebuffer(); PTR(void) osViGetCurrentFramebuffer(); u32 osGetCount(); +void osSetCount(u32 count); OSTime osGetTime(); +void osSetTime(OSTime t); int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg); int osStopTimer(RDRAM_ARG PTR(OSTimer) timer); u32 osVirtualToPhysical(PTR(void) addr); diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp index 7fb890b..46f63f1 100644 --- a/ultramodern/src/events.cpp +++ b/ultramodern/src/events.cpp @@ -28,6 +28,7 @@ struct SpTaskAction { }; struct ScreenUpdateAction { + ultramodern::renderer::ViRegs regs; }; struct UpdateConfigAction { @@ -55,6 +56,7 @@ static struct { int field; ViState states[2]; ultramodern::renderer::ViRegs regs; + ultramodern::renderer::ViRegs update_screen_regs; ViState* get_next_state() { return &states[cur_state ^ 1]; @@ -131,7 +133,7 @@ static struct { } events_context{}; ultramodern::renderer::ViRegs* ultramodern::renderer::get_vi_regs() { - return &events_context.vi.regs; + return &events_context.vi.update_screen_regs; } extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { @@ -198,8 +200,9 @@ void vi_thread_func() { next = std::chrono::high_resolution_clock::now(); } ultramodern::sleep_until(next); + auto time_now = ultramodern::time_since_start(); // Calculate how many VIs have passed - uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; + uint64_t new_total_vis = (time_now * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; if (new_total_vis > total_vis + 1) { //printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1); } @@ -212,12 +215,13 @@ void vi_thread_func() { odd = !odd; } + // Queue a screen update for the graphics thread with the current VI register state. + // Doing this before the VI update is equivalent to updating the screen after the previous frame's scanout finished. + events_context.action_queue.enqueue(ScreenUpdateAction{ events_context.vi.regs }); + // Update VI registers and swap VI modes. events_context.vi.update_vi(); - // Queue a screen update for the graphics thread. - events_context.action_queue.enqueue(ScreenUpdateAction{ }); - // If the game has started, handle sending VI and AI events. if (ultramodern::is_game_started()) { remaining_retraces--; @@ -226,13 +230,12 @@ void vi_thread_func() { std::lock_guard lock{ events_context.message_mutex }; ViState* cur_state = events_context.vi.get_cur_state(); if (remaining_retraces == 0) { - remaining_retraces = cur_state->retrace_count; - if (cur_state->mq != NULLPTR) { if (osSendMesg(PASS_RDRAM cur_state->mq, cur_state->msg, OS_MESG_NOBLOCK) == -1) { //printf("Game skipped a VI frame!\n"); } } + remaining_retraces = cur_state->retrace_count; } if (events_context.ai.mq != NULLPTR) { if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { @@ -370,7 +373,7 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re // printf("Renderer ProcessDList time: %d us\n", static_cast(std::chrono::duration_cast(renderer_end - renderer_start).count())); } else if (const auto* screen_update_action = std::get_if(&action)) { - (void)screen_update_action; + events_context.vi.update_screen_regs = screen_update_action->regs; renderer_context->update_screen(); display_refresh_rate = renderer_context->get_display_framerate(); resolution_scale = renderer_context->get_resolution_scale(); diff --git a/ultramodern/src/timer.cpp b/ultramodern/src/timer.cpp index 1b4e9ae..d36ffcc 100644 --- a/ultramodern/src/timer.cpp +++ b/ultramodern/src/timer.cpp @@ -13,6 +13,8 @@ // Start time for the program static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); +// Offset of the duration since program start used to calculate the value for osGetTime. +static int64_t ostime_offset = 0; // Game speed multiplier (1 means no speedup) constexpr uint32_t speed_multiplier = 1; // N64 CPU counter ticks per millisecond @@ -162,12 +164,20 @@ extern "C" u32 osGetCount() { return (uint32_t)total_count; } +extern "C" void osSetCount(u32 count) { + assert(false); +} + extern "C" OSTime osGetTime() { - uint64_t total_count = time_now(); + uint64_t total_count = time_now() - ostime_offset; return total_count; } +extern "C" void osSetTime(OSTime t) { + ostime_offset = time_now() - t; +} + extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) { OSTimer* t = TO_PTR(OSTimer, t_);