From b6f871d2b9638c14b7a7b689a3de44d55b071e5a Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 31 Jul 2020 17:09:18 +1000 Subject: [PATCH] JIT optimizations and refactoring (#675) * CPU/Recompiler: Use rel32 call where possible for no-args * JitCodeBuffer: Support using preallocated buffer * CPU/Recompiler/AArch64: Use bl instead of blr for short branches * CPU/CodeCache: Allocate recompiler buffer in program space This means we don't need 64-bit moves for every call out of the recompiler. * GTE: Don't store as u16 and load as u32 * CPU/Recompiler: Add methods to emit global load/stores * GTE: Convert class to namespace * CPU/Recompiler: Call GTE functions directly * Settings: Turn into a global variable * GPU: Replace local pointers with global * InterruptController: Turn into a global pointer * System: Replace local pointers with global * Timers: Turn into a global instance * DMA: Turn into a global instance * SPU: Turn into a global instance * CDROM: Turn into a global instance * MDEC: Turn into a global instance * Pad: Turn into a global instance * SIO: Turn into a global instance * CDROM: Move audio FIFO to the heap * CPU/Recompiler: Drop ASMFunctions No longer needed since we have code in the same 4GB window. * CPUCodeCache: Turn class into namespace * Bus: Local pointer -> global pointers * CPU: Turn class into namespace * Bus: Turn into namespace * GTE: Store registers in CPU state struct Allows relative addressing on ARM. * CPU/Recompiler: Align code storage to page size * CPU/Recompiler: Fix relative branches on A64 * HostInterface: Local references to global * System: Turn into a namespace, move events out * Add guard pages * Android: Fix build --- .../app/src/cpp/android_host_interface.cpp | 76 +- src/common/jit_code_buffer.cpp | 129 +- src/common/jit_code_buffer.h | 28 +- src/core/CMakeLists.txt | 4 - src/core/analog_controller.cpp | 25 +- src/core/analog_controller.h | 7 +- src/core/bus.cpp | 1470 ++++++++++----- src/core/bus.h | 414 ++--- src/core/bus.inl | 288 --- src/core/cdrom.cpp | 44 +- src/core/cdrom.h | 18 +- src/core/controller.cpp | 10 +- src/core/controller.h | 5 +- src/core/core.vcxproj | 6 - src/core/core.vcxproj.filters | 8 +- src/core/cpu_code_cache.cpp | 400 ++-- src/core/cpu_code_cache.h | 84 +- src/core/cpu_core.cpp | 788 ++++---- src/core/cpu_core.h | 253 +-- src/core/cpu_core.inl | 146 -- src/core/cpu_disasm.cpp | 37 +- src/core/cpu_disasm.h | 4 +- src/core/cpu_recompiler_code_generator.cpp | 90 +- src/core/cpu_recompiler_code_generator.h | 6 +- .../cpu_recompiler_code_generator_aarch64.cpp | 195 +- .../cpu_recompiler_code_generator_generic.cpp | 4 +- .../cpu_recompiler_code_generator_x64.cpp | 242 ++- src/core/cpu_recompiler_thunks.cpp | 167 -- src/core/cpu_recompiler_thunks.h | 84 +- src/core/cpu_recompiler_types.h | 9 +- src/core/cpu_types.h | 2 - src/core/dma.cpp | 79 +- src/core/dma.h | 22 +- src/core/gpu.cpp | 76 +- src/core/gpu.h | 12 +- src/core/gpu_commands.cpp | 6 +- src/core/gpu_hw.cpp | 37 +- src/core/gpu_hw.h | 3 +- src/core/gpu_hw_d3d11.cpp | 18 +- src/core/gpu_hw_d3d11.h | 3 +- src/core/gpu_hw_opengl.cpp | 11 +- src/core/gpu_hw_opengl.h | 3 +- src/core/gpu_hw_vulkan.cpp | 9 +- src/core/gpu_hw_vulkan.h | 3 +- src/core/gpu_sw.cpp | 7 +- src/core/gpu_sw.h | 3 +- src/core/gte.cpp | 1622 ++++++++++------- src/core/gte.h | 121 +- src/core/gte.inl | 117 -- src/core/gte_types.h | 12 +- src/core/host_interface.cpp | 248 +-- src/core/host_interface.h | 24 +- src/core/host_interface_progress_callback.cpp | 39 +- src/core/host_interface_progress_callback.h | 3 +- src/core/interrupt_controller.cpp | 12 +- src/core/interrupt_controller.h | 15 +- src/core/mdec.cpp | 27 +- src/core/mdec.h | 10 +- src/core/memory_card.cpp | 21 +- src/core/memory_card.h | 8 +- src/core/namco_guncon.cpp | 21 +- src/core/namco_guncon.h | 7 +- src/core/pad.cpp | 47 +- src/core/pad.h | 12 +- src/core/playstation_mouse.cpp | 12 +- src/core/playstation_mouse.h | 6 +- src/core/settings.cpp | 2 + src/core/settings.h | 5 + src/core/sio.cpp | 12 +- src/core/sio.h | 10 +- src/core/spu.cpp | 40 +- src/core/spu.h | 15 +- src/core/system.cpp | 981 +++++----- src/core/system.h | 337 +--- src/core/timers.cpp | 33 +- src/core/timers.h | 19 +- src/core/timing_event.cpp | 266 ++- src/core/timing_event.h | 40 +- .../libretro_host_interface.cpp | 133 +- .../libretro_host_interface.h | 2 +- src/duckstation-qt/qthostinterface.cpp | 65 +- src/duckstation-sdl/sdl_host_interface.cpp | 86 +- src/duckstation-sdl/sdl_host_interface.h | 3 - src/frontend-common/common_host_interface.cpp | 250 ++- src/frontend-common/common_host_interface.h | 1 - src/frontend-common/controller_interface.cpp | 11 - src/frontend-common/controller_interface.h | 3 - .../save_state_selector_ui.cpp | 5 +- 88 files changed, 4993 insertions(+), 5045 deletions(-) delete mode 100644 src/core/bus.inl delete mode 100644 src/core/cpu_core.inl delete mode 100644 src/core/cpu_recompiler_thunks.cpp delete mode 100644 src/core/gte.inl diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 5b9047365..7a54fe092 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -9,9 +9,9 @@ #include "core/gpu.h" #include "core/host_display.h" #include "core/system.h" +#include "frontend-common/imgui_styles.h" #include "frontend-common/opengl_host_display.h" #include "frontend-common/vulkan_host_display.h" -#include "frontend-common/imgui_styles.h" #include #include #include @@ -242,27 +242,23 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf m_callback_mutex.unlock(); // simulate the system if not paused - if (m_system && !m_paused) - m_system->RunFrame(); + if (System::IsRunning()) + System::RunFrame(); // rendering { DrawImGuiWindows(); - if (m_system) - m_system->GetGPU()->ResetGraphicsAPIState(); + g_gpu->ResetGraphicsAPIState(); m_display->Render(); ImGui::NewFrame(); - if (m_system) - { - m_system->GetGPU()->RestoreGraphicsAPIState(); - m_system->UpdatePerformanceCounters(); + g_gpu->RestoreGraphicsAPIState(); + System::UpdatePerformanceCounters(); - if (m_speed_limiter_enabled) - m_system->Throttle(); - } + if (m_speed_limiter_enabled) + System::Throttle(); } } @@ -280,7 +276,7 @@ bool AndroidHostInterface::AcquireHostDisplay() wi.surface_height = ANativeWindow_getHeight(m_surface); std::unique_ptr display; - switch (m_settings.gpu_renderer) + switch (g_settings.gpu_renderer) { case GPURenderer::HardwareVulkan: display = std::make_unique(); @@ -292,8 +288,8 @@ bool AndroidHostInterface::AcquireHostDisplay() break; } - if (!display->CreateRenderDevice(wi, {}, m_settings.gpu_use_debug_device) || - !display->InitializeRenderDevice(GetShaderCacheBasePath(), m_settings.gpu_use_debug_device)) + if (!display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device) || + !display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device)) { ReportError("Failed to acquire host display."); return false; @@ -363,15 +359,15 @@ void AndroidHostInterface::SetControllerType(u32 index, std::string_view type_na if (!IsEmulationThreadRunning()) { - m_settings.controller_types[index] = type; + g_settings.controller_types[index] = type; return; } RunOnEmulationThread( [this, index, type]() { Log_InfoPrintf("Changing controller slot %d to %s", index, Settings::GetControllerTypeName(type)); - m_settings.controller_types[index] = type; - m_system->UpdateControllers(); + g_settings.controller_types[index] = type; + System::UpdateControllers(); }, false); } @@ -383,7 +379,7 @@ void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code, RunOnEmulationThread( [this, index, button_code, pressed]() { - Controller* controller = m_system->GetController(index); + Controller* controller = System::GetController(index); if (!controller) return; @@ -398,14 +394,14 @@ void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, fl return; RunOnEmulationThread( - [this, index, button_code, value]() { - Controller* controller = m_system->GetController(index); - if (!controller) - return; + [this, index, button_code, value]() { + Controller* controller = System::GetController(index); + if (!controller) + return; - controller->SetAxisState(button_code, value); - }, - false); + controller->SetAxisState(button_code, value); + }, + false); } void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database) @@ -416,7 +412,7 @@ void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidat void AndroidHostInterface::ApplySettings() { - Settings old_settings = std::move(m_settings); + Settings old_settings = std::move(g_settings); CommonHostInterface::LoadSettings(m_settings_interface); CheckForSettingsChanges(old_settings); } @@ -565,21 +561,23 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisCode, jobject jstring axis_name) { std::optional type = - Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); + Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str()); if (!type) return -1; std::optional code = - Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name)); + Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name)); return code.value_or(-1); } -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, jboolean invalidate_database) +DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, + jboolean invalidate_database) { AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database); } -static const char* DiscRegionToString(DiscRegion region) { +static const char* DiscRegionToString(DiscRegion region) +{ static std::array names = {{"NTSC_J", "NTSC_U", "PAL", "Other"}}; return names[static_cast(region)]; } @@ -628,9 +626,7 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); if (hi->IsEmulationThreadRunning()) { - hi->RunOnEmulationThread([hi]() { - hi->ApplySettings(); - }); + hi->RunOnEmulationThread([hi]() { hi->ApplySettings(); }); } else { @@ -641,23 +637,17 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_resetSystem, jobject obj, jboolean global, jint slot) { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi]() { - hi->ResetSystem(); - }); + hi->RunOnEmulationThread([hi]() { hi->ResetSystem(); }); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_loadState, jobject obj, jboolean global, jint slot) { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi, global, slot]() { - hi->LoadState(global, slot); - }); + hi->RunOnEmulationThread([hi, global, slot]() { hi->LoadState(global, slot); }); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveState, jobject obj, jboolean global, jint slot) { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); - hi->RunOnEmulationThread([hi, global, slot]() { - hi->SaveState(global, slot); - }); + hi->RunOnEmulationThread([hi, global, slot]() { hi->SaveState(global, slot); }); } diff --git a/src/common/jit_code_buffer.cpp b/src/common/jit_code_buffer.cpp index 724cbbcdc..9dcc97e75 100644 --- a/src/common/jit_code_buffer.cpp +++ b/src/common/jit_code_buffer.cpp @@ -10,8 +10,29 @@ #include #endif -JitCodeBuffer::JitCodeBuffer(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_size /* = 0 */) +JitCodeBuffer::JitCodeBuffer() = default; + +JitCodeBuffer::JitCodeBuffer(u32 size, u32 far_code_size) { + if (!Allocate(size, far_code_size)) + Panic("Failed to allocate code space"); +} + +JitCodeBuffer::JitCodeBuffer(void* buffer, u32 size, u32 far_code_size, u32 guard_pages) +{ + if (!Initialize(buffer, size, far_code_size)) + Panic("Failed to initialize code space"); +} + +JitCodeBuffer::~JitCodeBuffer() +{ + Destroy(); +} + +bool JitCodeBuffer::Allocate(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_size /* = 0 */) +{ + Destroy(); + m_total_size = size + far_code_size; #if defined(WIN32) @@ -22,6 +43,10 @@ JitCodeBuffer::JitCodeBuffer(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_siz #else m_code_ptr = nullptr; #endif + + if (!m_code_ptr) + return false; + m_free_code_ptr = m_code_ptr; m_code_size = size; m_code_used = 0; @@ -31,17 +56,91 @@ JitCodeBuffer::JitCodeBuffer(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_siz m_far_code_size = far_code_size; m_far_code_used = 0; - if (!m_code_ptr) - Panic("Failed to allocate code space."); + m_old_protection = 0; + m_owns_buffer = true; + return true; } -JitCodeBuffer::~JitCodeBuffer() +bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0 */, u32 guard_size /* = 0 */) { + Destroy(); + + if ((far_code_size > 0 && guard_size >= far_code_size) || (far_code_size + (guard_size * 2)) > size) + return false; + #if defined(WIN32) - VirtualFree(m_code_ptr, 0, MEM_RELEASE); + DWORD old_protect = 0; + if (!VirtualProtect(buffer, size, PAGE_EXECUTE_READWRITE, &old_protect)) + return false; + + if (guard_size > 0) + { + DWORD old_guard_protect = 0; + u8* guard_at_end = (static_cast(buffer) + size) - guard_size; + if (!VirtualProtect(buffer, guard_size, PAGE_NOACCESS, &old_guard_protect) || + !VirtualProtect(guard_at_end, guard_size, PAGE_NOACCESS, &old_guard_protect)) + { + return false; + } + } + + m_code_ptr = static_cast(buffer); + m_old_protection = static_cast(old_protect); #elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) - munmap(m_code_ptr, m_total_size); + if (mprotect(buffer, size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) + return false; + + if (guard_size > 0) + { + u8* guard_at_end = (static_cast(buffer) + size) - guard_size; + if (mprotect(buffer, guard_size, PROT_NONE) != 0 || mprotect(guard_at_end, guard_size, PROT_NONE) != 0) + return false; + } + + // reasonable default? + m_code_ptr = static_cast(buffer); + m_old_protection = PROT_READ | PROT_WRITE; +#else + m_code_ptr = nullptr; #endif + + if (!m_code_ptr) + return false; + + m_total_size = size; + m_free_code_ptr = m_code_ptr + guard_size; + m_code_size = size - far_code_size - (guard_size * 2); + m_code_used = 0; + + m_far_code_ptr = static_cast(m_code_ptr) + m_code_size; + m_free_far_code_ptr = m_far_code_ptr; + m_far_code_size = far_code_size - guard_size; + m_far_code_used = 0; + + m_guard_size = guard_size; + m_owns_buffer = false; + return true; +} + +void JitCodeBuffer::Destroy() +{ + if (m_owns_buffer) + { +#if defined(WIN32) + VirtualFree(m_code_ptr, 0, MEM_RELEASE); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) + munmap(m_code_ptr, m_total_size); +#endif + } + else if (m_code_ptr) + { +#if defined(WIN32) + DWORD old_protect = 0; + VirtualProtect(m_code_ptr, m_total_size, m_old_protection, &old_protect); +#else + mprotect(m_code_ptr, m_total_size, m_old_protection); +#endif + } } void JitCodeBuffer::CommitCode(u32 length) @@ -76,18 +175,18 @@ void JitCodeBuffer::CommitFarCode(u32 length) void JitCodeBuffer::Reset() { - std::memset(m_code_ptr, 0, m_code_size); - FlushInstructionCache(m_code_ptr, m_code_size); + m_free_code_ptr = m_code_ptr + m_guard_size; + m_code_used = 0; + std::memset(m_free_code_ptr, 0, m_code_size); + FlushInstructionCache(m_free_code_ptr, m_code_size); + if (m_far_code_size > 0) { - std::memset(m_far_code_ptr, 0, m_far_code_size); - FlushInstructionCache(m_far_code_ptr, m_far_code_size); + m_free_far_code_ptr = m_far_code_ptr; + m_far_code_used = 0; + std::memset(m_free_far_code_ptr, 0, m_far_code_size); + FlushInstructionCache(m_free_far_code_ptr, m_far_code_size); } - m_free_code_ptr = m_code_ptr; - m_code_used = 0; - - m_free_far_code_ptr = m_far_code_ptr; - m_far_code_used = 0; } void JitCodeBuffer::Align(u32 alignment, u8 padding_value) diff --git a/src/common/jit_code_buffer.h b/src/common/jit_code_buffer.h index eaf2f28bb..64957d69e 100644 --- a/src/common/jit_code_buffer.h +++ b/src/common/jit_code_buffer.h @@ -4,9 +4,14 @@ class JitCodeBuffer { public: - JitCodeBuffer(u32 size = 64 * 1024 * 1024, u32 far_code_size = 0); + JitCodeBuffer(); + JitCodeBuffer(u32 size, u32 far_code_size); + JitCodeBuffer(void* buffer, u32 size, u32 far_code_size, u32 guard_size); ~JitCodeBuffer(); + bool Allocate(u32 size = 64 * 1024 * 1024, u32 far_code_size = 0); + bool Initialize(void* buffer, u32 size, u32 far_code_size = 0, u32 guard_size = 0); + void Destroy(); void Reset(); u8* GetFreeCodePointer() const { return m_free_code_ptr; } @@ -25,16 +30,19 @@ public: static void FlushInstructionCache(void* address, u32 size); private: - u8* m_code_ptr; - u8* m_free_code_ptr; - u32 m_code_size; - u32 m_code_used; + u8* m_code_ptr = nullptr; + u8* m_free_code_ptr = nullptr; + u32 m_code_size = 0; + u32 m_code_used = 0; - u8* m_far_code_ptr; - u8* m_free_far_code_ptr; - u32 m_far_code_size; - u32 m_far_code_used; + u8* m_far_code_ptr = nullptr; + u8* m_free_far_code_ptr = nullptr; + u32 m_far_code_size = 0; + u32 m_far_code_used = 0; - u32 m_total_size; + u32 m_total_size = 0; + u32 m_guard_size = 0; + u32 m_old_protection = 0; + bool m_owns_buffer = false; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f678531e6..ddac448f6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -5,7 +5,6 @@ add_library(core bios.h bus.cpp bus.h - bus.inl cdrom.cpp cdrom.h cdrom_async_reader.cpp @@ -16,7 +15,6 @@ add_library(core cpu_code_cache.h cpu_core.cpp cpu_core.h - cpu_core.inl cpu_disasm.cpp cpu_disasm.h cpu_types.cpp @@ -42,7 +40,6 @@ add_library(core gpu_sw.h gte.cpp gte.h - gte.inl gte_types.h host_display.cpp host_display.h @@ -90,7 +87,6 @@ set(RECOMPILER_SRCS cpu_recompiler_code_generator_generic.cpp cpu_recompiler_register_cache.cpp cpu_recompiler_register_cache.h - cpu_recompiler_thunks.cpp cpu_recompiler_thunks.h cpu_recompiler_types.h ) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 0dc316982..08cf3582b 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -6,9 +6,10 @@ #include "system.h" Log_SetChannel(AnalogController); -AnalogController::AnalogController(System* system, u32 index) : m_system(system), m_index(index) +AnalogController::AnalogController(u32 index) : m_index(index) { m_axis_state.fill(0x80); + Reset(); } AnalogController::~AnalogController() = default; @@ -52,8 +53,8 @@ bool AnalogController::DoState(StateWrapper& sw) if (old_analog_mode != m_analog_mode) { - m_system->GetHostInterface()->AddFormattedOSDMessage(2.0f, "Controller %u switched to %s mode.", m_index + 1u, - m_analog_mode ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage(2.0f, "Controller %u switched to %s mode.", m_index + 1u, + m_analog_mode ? "analog" : "digital"); } } return true; @@ -94,8 +95,8 @@ void AnalogController::SetButtonState(Button button, bool pressed) { if (m_analog_locked) { - m_system->GetHostInterface()->AddFormattedOSDMessage(2.0f, "Controller %u is locked to %s mode by the game.", - m_index + 1u, m_analog_mode ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage(2.0f, "Controller %u is locked to %s mode by the game.", m_index + 1u, + m_analog_mode ? "analog" : "digital"); } else { @@ -154,8 +155,8 @@ void AnalogController::SetAnalogMode(bool enabled) return; Log_InfoPrintf("Controller %u switched to %s mode.", m_index + 1u, enabled ? "analog" : "digital"); - m_system->GetHostInterface()->AddFormattedOSDMessage(2.0f, "Controller %u switched to %s mode.", m_index + 1u, - enabled ? "analog" : "digital"); + g_host_interface->AddFormattedOSDMessage(2.0f, "Controller %u switched to %s mode.", m_index + 1u, + enabled ? "analog" : "digital"); m_analog_mode = enabled; } @@ -397,9 +398,9 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out) return ack; } -std::unique_ptr AnalogController::Create(System* system, u32 index) +std::unique_ptr AnalogController::Create(u32 index) { - return std::make_unique(system, index); + return std::make_unique(index); } std::optional AnalogController::StaticGetAxisCodeByName(std::string_view axis_name) @@ -492,8 +493,8 @@ Controller::SettingList AnalogController::StaticGetSettings() return SettingList(settings.begin(), settings.end()); } -void AnalogController::LoadSettings(HostInterface* host_interface, const char* section) +void AnalogController::LoadSettings(const char* section) { - Controller::LoadSettings(host_interface, section); - m_auto_enable_analog = host_interface->GetBoolSettingValue(section, "AutoEnableAnalog", false); + Controller::LoadSettings(section); + m_auto_enable_analog = g_host_interface->GetBoolSettingValue(section, "AutoEnableAnalog", false); } diff --git a/src/core/analog_controller.h b/src/core/analog_controller.h index 8679323e6..1774b4305 100644 --- a/src/core/analog_controller.h +++ b/src/core/analog_controller.h @@ -41,10 +41,10 @@ public: static constexpr u8 NUM_MOTORS = 2; - AnalogController(System* system, u32 index); + AnalogController(u32 index); ~AnalogController() override; - static std::unique_ptr Create(System* system, u32 index); + static std::unique_ptr Create(u32 index); static std::optional StaticGetAxisCodeByName(std::string_view axis_name); static std::optional StaticGetButtonCodeByName(std::string_view button_name); static AxisList StaticGetAxisNames(); @@ -71,7 +71,7 @@ public: u32 GetVibrationMotorCount() const override; float GetVibrationMotorStrength(u32 motor) override; - void LoadSettings(HostInterface* host_interface, const char* section) override; + void LoadSettings(const char* section) override; private: using MotorState = std::array; @@ -132,7 +132,6 @@ private: void SetAnalogMode(bool enabled); void SetMotorState(u8 motor, u8 value); - System* m_system; u32 m_index; bool m_auto_enable_analog = false; diff --git a/src/core/bus.cpp b/src/core/bus.cpp index c1bd37eb1..3334c75bc 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -16,8 +16,77 @@ #include "spu.h" #include "timers.h" #include +#include Log_SetChannel(Bus); +namespace Bus { + +union MEMDELAY +{ + u32 bits; + + BitField access_time; // cycles + BitField use_com0_time; + BitField use_com1_time; + BitField use_com2_time; + BitField use_com3_time; + BitField data_bus_16bit; + BitField memory_window_size; + + static constexpr u32 WRITE_MASK = 0b10101111'00011111'11111111'11111111; +}; + +union COMDELAY +{ + u32 bits; + + BitField com0; + BitField com1; + BitField com2; + BitField com3; + BitField comunk; + + static constexpr u32 WRITE_MASK = 0b00000000'00000011'11111111'11111111; +}; + +union MEMCTRL +{ + u32 regs[MEMCTRL_REG_COUNT]; + + struct + { + u32 exp1_base; + u32 exp2_base; + MEMDELAY exp1_delay_size; + MEMDELAY exp3_delay_size; + MEMDELAY bios_delay_size; + MEMDELAY spu_delay_size; + MEMDELAY cdrom_delay_size; + MEMDELAY exp2_delay_size; + COMDELAY common_delay; + }; +}; + +std::bitset m_ram_code_bits{}; +u8 g_ram[RAM_SIZE]{}; // 2MB RAM +u8 g_bios[BIOS_SIZE]{}; // 512K BIOS ROM + +static std::array m_exp1_access_time = {}; +static std::array m_exp2_access_time = {}; +static std::array m_bios_access_time = {}; +static std::array m_cdrom_access_time = {}; +static std::array m_spu_access_time = {}; + +static std::vector m_exp1_rom; + +static MEMCTRL m_MEMCTRL = {}; +static u32 m_ram_size_reg = 0; + +static std::string m_tty_line_buffer; + +static std::tuple CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay); +static void RecalculateMemoryTimings(); + #define FIXUP_WORD_READ_OFFSET(offset) ((offset) & ~u32(3)) #define FIXUP_WORD_READ_VALUE(offset, value) ((value) >> (((offset)&u32(3)) * 8u)) #define FIXUP_HALFWORD_READ_OFFSET(offset) ((offset) & ~u32(1)) @@ -25,6 +94,7 @@ Log_SetChannel(Bus); #define FIXUP_HALFWORD_WRITE_VALUE(offset, value) ((value) << (((offset)&u32(1)) * 8u)) // Offset and value remapping for (w32) registers from nocash docs. +// TODO: Make template function based on type, and noop for word access ALWAYS_INLINE static void FixupUnalignedWordAccessW32(u32& offset, u32& value) { const u32 byte_offset = offset & u32(3); @@ -32,30 +102,19 @@ ALWAYS_INLINE static void FixupUnalignedWordAccessW32(u32& offset, u32& value) value <<= byte_offset * 8; } -Bus::Bus() = default; - -Bus::~Bus() = default; - -void Bus::Initialize(CPU::Core* cpu, CPU::CodeCache* cpu_code_cache, DMA* dma, - InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers, - SPU* spu, MDEC* mdec, SIO* sio) +void Initialize() { - m_cpu = cpu; - m_cpu_code_cache = cpu_code_cache; - m_dma = dma; - m_interrupt_controller = interrupt_controller; - m_gpu = gpu; - m_cdrom = cdrom; - m_pad = pad; - m_timers = timers; - m_spu = spu; - m_mdec = mdec; - m_sio = sio; + Reset(); } -void Bus::Reset() +void Shutdown() { - std::memset(m_ram, 0, sizeof(m_ram)); + // +} + +void Reset() +{ + std::memset(g_ram, 0, sizeof(g_ram)); m_MEMCTRL.exp1_base = 0x1F000000; m_MEMCTRL.exp2_base = 0x1F802000; m_MEMCTRL.exp1_delay_size.bits = 0x0013243F; @@ -66,116 +125,31 @@ void Bus::Reset() m_MEMCTRL.exp2_delay_size.bits = 0x00070777; m_MEMCTRL.common_delay.bits = 0x00031125; m_ram_size_reg = UINT32_C(0x00000B88); + m_ram_code_bits = {}; RecalculateMemoryTimings(); } -bool Bus::DoState(StateWrapper& sw) +bool DoState(StateWrapper& sw) { sw.Do(&m_exp1_access_time); sw.Do(&m_exp2_access_time); sw.Do(&m_bios_access_time); sw.Do(&m_cdrom_access_time); sw.Do(&m_spu_access_time); - sw.DoBytes(m_ram, sizeof(m_ram)); - sw.DoBytes(m_bios, sizeof(m_bios)); + sw.DoBytes(g_ram, sizeof(g_ram)); + sw.DoBytes(g_bios, sizeof(g_bios)); sw.DoArray(m_MEMCTRL.regs, countof(m_MEMCTRL.regs)); sw.Do(&m_ram_size_reg); sw.Do(&m_tty_line_buffer); return !sw.HasError(); } -bool Bus::ReadByte(PhysicalMemoryAddress address, u8* value) -{ - u32 temp = 0; - const bool result = DispatchAccess(address, temp); - *value = Truncate8(temp); - return result; -} - -bool Bus::ReadHalfWord(PhysicalMemoryAddress address, u16* value) -{ - u32 temp = 0; - const bool result = DispatchAccess(address, temp); - *value = Truncate16(temp); - return result; -} - -bool Bus::ReadWord(PhysicalMemoryAddress address, u32* value) -{ - return DispatchAccess(address, *value); -} - -bool Bus::WriteByte(PhysicalMemoryAddress address, u8 value) -{ - u32 temp = ZeroExtend32(value); - return DispatchAccess(address, temp); -} - -bool Bus::WriteHalfWord(PhysicalMemoryAddress address, u16 value) -{ - u32 temp = ZeroExtend32(value); - return DispatchAccess(address, temp); -} - -bool Bus::WriteWord(PhysicalMemoryAddress address, u32 value) -{ - return DispatchAccess(address, value); -} - -TickCount Bus::ReadWords(PhysicalMemoryAddress address, u32* words, u32 word_count) -{ - if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE)) - { - // Not RAM, or RAM mirrors. - TickCount total_ticks = 0; - for (u32 i = 0; i < word_count; i++) - { - const TickCount ticks = DispatchAccess(address, words[i]); - if (ticks < 0) - return -1; - - total_ticks += ticks; - address += sizeof(u32); - } - - return total_ticks; - } - - std::memcpy(words, &m_ram[address], sizeof(u32) * word_count); - return GetDMARAMTickCount(word_count); -} - -TickCount Bus::WriteWords(PhysicalMemoryAddress address, const u32* words, u32 word_count) -{ - if (address + (word_count * sizeof(u32)) > (RAM_BASE + RAM_SIZE)) - { - // Not RAM, or RAM mirrors. - TickCount total_ticks = 0; - for (u32 i = 0; i < word_count; i++) - { - u32 value = words[i]; - const TickCount ticks = DispatchAccess(address, value); - if (ticks < 0) - return -1; - - total_ticks += ticks; - address += sizeof(u32); - } - - return total_ticks; - } - - std::memcpy(&m_ram[address], words, sizeof(u32) * word_count); - InvalidateCodePages(address, word_count); - return GetDMARAMTickCount(word_count); -} - -void Bus::SetExpansionROM(std::vector data) +void SetExpansionROM(std::vector data) { m_exp1_rom = std::move(data); } -void Bus::SetBIOS(const std::vector& image) +void SetBIOS(const std::vector& image) { if (image.size() != static_cast(BIOS_SIZE)) { @@ -183,10 +157,10 @@ void Bus::SetBIOS(const std::vector& image) return; } - std::memcpy(m_bios, image.data(), BIOS_SIZE); + std::memcpy(g_bios, image.data(), BIOS_SIZE); } -std::tuple Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay) +std::tuple CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay) { // from nocash spec s32 first = 0, seq = 0, min = 0; @@ -222,7 +196,7 @@ std::tuple Bus::CalculateMemoryTiming(MEMDELAY std::max(word_access_time - 1, 0)); } -void Bus::RecalculateMemoryTimings() +void RecalculateMemoryTimings() { std::tie(m_bios_access_time[0], m_bios_access_time[1], m_bios_access_time[2]) = CalculateMemoryTiming(m_MEMCTRL.bios_delay_size, m_MEMCTRL.common_delay); @@ -242,7 +216,8 @@ void Bus::RecalculateMemoryTimings() m_spu_access_time[2] + 1); } -TickCount Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value) +static TickCount DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, + u32& value) { SmallString str; str.AppendString("Invalid bus "); @@ -269,347 +244,976 @@ TickCount Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, Phy return 1; } -u32 Bus::DoReadEXP1(MemoryAccessSize size, u32 offset) +template +ALWAYS_INLINE static TickCount DoRAMAccess(u32 offset, u32& value) { - if (m_exp1_rom.empty()) + // TODO: Configurable mirroring. + offset &= UINT32_C(0x1FFFFF); + if constexpr (type == MemoryAccessType::Read) { - // EXP1 not present. - return UINT32_C(0xFFFFFFFF); - } - - if (offset == 0x20018) - { - // Bit 0 - Action Replay On/Off - return UINT32_C(1); - } - - const u32 transfer_size = u32(1) << static_cast(size); - if ((offset + transfer_size) > m_exp1_rom.size()) - { - return UINT32_C(0); - } - - u32 value; - if (size == MemoryAccessSize::Byte) - { - value = ZeroExtend32(m_exp1_rom[offset]); - } - else if (size == MemoryAccessSize::HalfWord) - { - u16 halfword; - std::memcpy(&halfword, &m_exp1_rom[offset], sizeof(halfword)); - value = ZeroExtend32(halfword); + if constexpr (size == MemoryAccessSize::Byte) + { + value = ZeroExtend32(g_ram[offset]); + } + else if constexpr (size == MemoryAccessSize::HalfWord) + { + u16 temp; + std::memcpy(&temp, &g_ram[offset], sizeof(u16)); + value = ZeroExtend32(temp); + } + else if constexpr (size == MemoryAccessSize::Word) + { + std::memcpy(&value, &g_ram[offset], sizeof(u32)); + } } else { - std::memcpy(&value, &m_exp1_rom[offset], sizeof(value)); - } + const u32 page_index = offset / CPU_CODE_CACHE_PAGE_SIZE; + if (m_ram_code_bits[page_index]) + CPU::CodeCache::InvalidateBlocksWithPageIndex(page_index); - // Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", EXP1_BASE | offset, value); - return value; -} - -void Bus::DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value) -{ - Log_WarningPrintf("EXP1 write: 0x%08X <- 0x%08X", EXP1_BASE | offset, value); -} - -u32 Bus::DoReadEXP2(MemoryAccessSize size, u32 offset) -{ - // rx/tx buffer empty - if (offset == 0x21) - { - return 0x04 | 0x08; - } - - Log_WarningPrintf("EXP2 read: 0x%08X", EXP2_BASE | offset); - return UINT32_C(0xFFFFFFFF); -} - -void Bus::DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value) -{ - if (offset == 0x23) - { - if (value == '\r') - return; - - if (value == '\n') + if constexpr (size == MemoryAccessSize::Byte) { - if (!m_tty_line_buffer.empty()) - { - Log_InfoPrintf("TTY: %s", m_tty_line_buffer.c_str()); -#ifdef _DEBUG - if (CPU::LOG_EXECUTION) - CPU::WriteToExecutionLog("TTY: %s\n", m_tty_line_buffer.c_str()); -#endif - } - m_tty_line_buffer.clear(); + g_ram[offset] = Truncate8(value); + } + else if constexpr (size == MemoryAccessSize::HalfWord) + { + const u16 temp = Truncate16(value); + std::memcpy(&g_ram[offset], &temp, sizeof(u16)); + } + else if constexpr (size == MemoryAccessSize::Word) + { + std::memcpy(&g_ram[offset], &value, sizeof(u32)); + } + } + + return (type == MemoryAccessType::Read) ? 4 : 0; +} + +template +ALWAYS_INLINE static TickCount DoBIOSAccess(u32 offset, u32& value) +{ + // TODO: Configurable mirroring. + if constexpr (type == MemoryAccessType::Read) + { + offset &= UINT32_C(0x7FFFF); + if constexpr (size == MemoryAccessSize::Byte) + { + value = ZeroExtend32(g_bios[offset]); + } + else if constexpr (size == MemoryAccessSize::HalfWord) + { + u16 temp; + std::memcpy(&temp, &g_bios[offset], sizeof(u16)); + value = ZeroExtend32(temp); } else { - m_tty_line_buffer += static_cast(Truncate8(value)); - } - - return; - } - - if (offset == 0x41) - { - Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F)); - return; - } - - Log_WarningPrintf("EXP2 write: 0x%08X <- 0x%08X", EXP2_BASE | offset, value); -} - -u32 Bus::DoReadMemoryControl(MemoryAccessSize size, u32 offset) -{ - u32 value = m_MEMCTRL.regs[offset / 4]; - FixupUnalignedWordAccessW32(offset, value); - return value; -} - -void Bus::DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value) -{ - FixupUnalignedWordAccessW32(offset, value); - - const u32 index = offset / 4; - const u32 write_mask = (index == 8) ? COMDELAY::WRITE_MASK : MEMDELAY::WRITE_MASK; - const u32 new_value = (m_MEMCTRL.regs[index] & ~write_mask) | (value & write_mask); - if (m_MEMCTRL.regs[index] != new_value) - { - m_MEMCTRL.regs[index] = new_value; - RecalculateMemoryTimings(); - } -} - -u32 Bus::DoReadMemoryControl2(MemoryAccessSize size, u32 offset) -{ - if (offset == 0x00) - return m_ram_size_reg; - - u32 value = 0; - DoInvalidAccess(MemoryAccessType::Read, size, MEMCTRL2_BASE | offset, value); - return value; -} - -void Bus::DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value) -{ - if (offset == 0x00) - { - m_ram_size_reg = value; - return; - } - - DoInvalidAccess(MemoryAccessType::Write, size, MEMCTRL2_BASE | offset, value); -} - -u32 Bus::DoReadPad(MemoryAccessSize size, u32 offset) -{ - return m_pad->ReadRegister(offset); -} - -void Bus::DoWritePad(MemoryAccessSize size, u32 offset, u32 value) -{ - m_pad->WriteRegister(offset, value); -} - -u32 Bus::DoReadSIO(MemoryAccessSize size, u32 offset) -{ - return m_sio->ReadRegister(offset); -} - -void Bus::DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value) -{ - m_sio->WriteRegister(offset, value); -} - -u32 Bus::DoReadCDROM(MemoryAccessSize size, u32 offset) -{ - switch (size) - { - case MemoryAccessSize::Word: - { - const u32 b0 = ZeroExtend32(m_cdrom->ReadRegister(offset)); - const u32 b1 = ZeroExtend32(m_cdrom->ReadRegister(offset + 1u)); - const u32 b2 = ZeroExtend32(m_cdrom->ReadRegister(offset + 2u)); - const u32 b3 = ZeroExtend32(m_cdrom->ReadRegister(offset + 3u)); - return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); - } - - case MemoryAccessSize::HalfWord: - { - const u32 lsb = ZeroExtend32(m_cdrom->ReadRegister(offset)); - const u32 msb = ZeroExtend32(m_cdrom->ReadRegister(offset + 1u)); - return lsb | (msb << 8); - } - - case MemoryAccessSize::Byte: - default: - return ZeroExtend32(m_cdrom->ReadRegister(offset)); - } -} - -void Bus::DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value) -{ - switch (size) - { - case MemoryAccessSize::Word: - { - m_cdrom->WriteRegister(offset, Truncate8(value & 0xFFu)); - m_cdrom->WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu)); - m_cdrom->WriteRegister(offset + 2u, Truncate8((value >> 16) & 0xFFu)); - m_cdrom->WriteRegister(offset + 3u, Truncate8((value >> 24) & 0xFFu)); - } - break; - - case MemoryAccessSize::HalfWord: - { - m_cdrom->WriteRegister(offset, Truncate8(value & 0xFFu)); - m_cdrom->WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu)); - } - break; - - case MemoryAccessSize::Byte: - default: - return m_cdrom->WriteRegister(offset, Truncate8(value)); - } -} - -u32 Bus::DoReadGPU(MemoryAccessSize size, u32 offset) -{ - u32 value = m_gpu->ReadRegister(offset); - FixupUnalignedWordAccessW32(offset, value); - return value; -} - -void Bus::DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value) -{ - FixupUnalignedWordAccessW32(offset, value); - m_gpu->WriteRegister(offset, value); -} - -u32 Bus::DoReadMDEC(MemoryAccessSize size, u32 offset) -{ - u32 value = m_mdec->ReadRegister(offset); - FixupUnalignedWordAccessW32(offset, value); - return value; -} - -void Bus::DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value) -{ - FixupUnalignedWordAccessW32(offset, value); - m_mdec->WriteRegister(offset, value); -} - -u32 Bus::DoReadInterruptController(MemoryAccessSize size, u32 offset) -{ - u32 value = m_interrupt_controller->ReadRegister(offset); - FixupUnalignedWordAccessW32(offset, value); - return value; -} - -void Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value) -{ - FixupUnalignedWordAccessW32(offset, value); - m_interrupt_controller->WriteRegister(offset, value); -} - -u32 Bus::DoReadTimers(MemoryAccessSize size, u32 offset) -{ - u32 value = m_timers->ReadRegister(offset); - FixupUnalignedWordAccessW32(offset, value); - return value; -} - -void Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value) -{ - FixupUnalignedWordAccessW32(offset, value); - m_timers->WriteRegister(offset, value); -} - -u32 Bus::DoReadSPU(MemoryAccessSize size, u32 offset) -{ - switch (size) - { - case MemoryAccessSize::Word: - { - // 32-bit reads are read as two 16-bit accesses. - const u16 lsb = m_spu->ReadRegister(offset); - const u16 msb = m_spu->ReadRegister(offset + 2); - return ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16); - } - - case MemoryAccessSize::HalfWord: - { - return ZeroExtend32(m_spu->ReadRegister(offset)); - } - - case MemoryAccessSize::Byte: - default: - { - u16 value = m_spu->ReadRegister(FIXUP_HALFWORD_READ_OFFSET(offset)); - return FIXUP_HALFWORD_READ_VALUE(offset, value); + std::memcpy(&value, &g_bios[offset], sizeof(u32)); } } -} - -void Bus::DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value) -{ - // 32-bit writes are written as two 16-bit writes. - // TODO: Ignore if address is not aligned. - switch (size) + else { - case MemoryAccessSize::Word: - { - DebugAssert(Common::IsAlignedPow2(offset, 2)); - m_spu->WriteRegister(offset, Truncate16(value)); - m_spu->WriteRegister(offset + 2, Truncate16(value >> 16)); - return; - } - - case MemoryAccessSize::HalfWord: - { - DebugAssert(Common::IsAlignedPow2(offset, 2)); - m_spu->WriteRegister(offset, Truncate16(value)); - return; - } - - case MemoryAccessSize::Byte: - { - m_spu->WriteRegister(FIXUP_HALFWORD_READ_OFFSET(offset), Truncate16(FIXUP_HALFWORD_READ_VALUE(offset, value))); - return; - } + // Writes are ignored. } + + return m_bios_access_time[static_cast(size)]; } -void Bus::DoInvalidateCodeCache(u32 page_index) +template +ALWAYS_INLINE static TickCount DoEXP1Access(u32 offset, u32& value) { - m_cpu_code_cache->InvalidateBlocksWithPageIndex(page_index); -} - -u32 Bus::DoReadDMA(MemoryAccessSize size, u32 offset) -{ - return FIXUP_WORD_READ_VALUE(offset, m_dma->ReadRegister(FIXUP_WORD_READ_OFFSET(offset))); -} - -void Bus::DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value) -{ - switch (size) + if constexpr (type == MemoryAccessType::Read) { - case MemoryAccessSize::Byte: - case MemoryAccessSize::HalfWord: + if (m_exp1_rom.empty()) { - // zero extend length register - if ((offset & u32(0xF0)) < 7 && (offset & u32(0x0F)) == 0x4) - value = ZeroExtend32(value); + // EXP1 not present. + value = UINT32_C(0xFFFFFFFF); + } + else if (offset == 0x20018) + { + // Bit 0 - Action Replay On/Off + value = UINT32_C(1); + } + else + { + const u32 transfer_size = u32(1) << static_cast(size); + if ((offset + transfer_size) > m_exp1_rom.size()) + { + value = UINT32_C(0); + } else - FixupUnalignedWordAccessW32(offset, value); + { + if constexpr (size == MemoryAccessSize::Byte) + { + value = ZeroExtend32(m_exp1_rom[offset]); + } + else if constexpr (size == MemoryAccessSize::HalfWord) + { + u16 halfword; + std::memcpy(&halfword, &m_exp1_rom[offset], sizeof(halfword)); + value = ZeroExtend32(halfword); + } + else + { + std::memcpy(&value, &m_exp1_rom[offset], sizeof(value)); + } + + // Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", EXP1_BASE | offset, value); + } } - default: + return m_exp1_access_time[static_cast(size)]; + } + else + { + Log_WarningPrintf("EXP1 write: 0x%08X <- 0x%08X", EXP1_BASE | offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoEXP2Access(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + // rx/tx buffer empty + if (offset == 0x21) + { + value = 0x04 | 0x08; + } + else + { + Log_WarningPrintf("EXP2 read: 0x%08X", EXP2_BASE | offset); + value = UINT32_C(0xFFFFFFFF); + } + + return m_exp2_access_time[static_cast(size)]; + } + else + { + if (offset == 0x23) + { + if (value == '\r') + { + } + else if (value == '\n') + { + if (!m_tty_line_buffer.empty()) + { + Log_InfoPrintf("TTY: %s", m_tty_line_buffer.c_str()); +#ifdef _DEBUG + if (CPU::LOG_EXECUTION) + CPU::WriteToExecutionLog("TTY: %s\n", m_tty_line_buffer.c_str()); +#endif + } + m_tty_line_buffer.clear(); + } + else + { + m_tty_line_buffer += static_cast(Truncate8(value)); + } + } + else if (offset == 0x41) + { + Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F)); + } + else + { + Log_WarningPrintf("EXP2 write: 0x%08X <- 0x%08X", EXP2_BASE | offset, value); + } + + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoMemoryControlAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = m_MEMCTRL.regs[offset / 4]; + FixupUnalignedWordAccessW32(offset, value); + return 2; + } + else + { + FixupUnalignedWordAccessW32(offset, value); + + const u32 index = offset / 4; + const u32 write_mask = (index == 8) ? COMDELAY::WRITE_MASK : MEMDELAY::WRITE_MASK; + const u32 new_value = (m_MEMCTRL.regs[index] & ~write_mask) | (value & write_mask); + if (m_MEMCTRL.regs[index] != new_value) + { + m_MEMCTRL.regs[index] = new_value; + RecalculateMemoryTimings(); + } + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoMemoryControl2Access(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + if (offset == 0x00) + { + value = m_ram_size_reg; + } + else + { + return DoInvalidAccess(type, size, MEMCTRL2_BASE | offset, value); + } + + return 2; + } + else + { + if (offset == 0x00) + { + m_ram_size_reg = value; + } + else + { + return DoInvalidAccess(type, size, MEMCTRL2_BASE | offset, value); + } + + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoPadAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_pad.ReadRegister(offset); + return 2; + } + else + { + g_pad.WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoSIOAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_sio.ReadRegister(offset); + return 2; + } + else + { + g_sio.WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoCDROMAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + switch (size) + { + case MemoryAccessSize::Word: + { + const u32 b0 = ZeroExtend32(g_cdrom.ReadRegister(offset)); + const u32 b1 = ZeroExtend32(g_cdrom.ReadRegister(offset + 1u)); + const u32 b2 = ZeroExtend32(g_cdrom.ReadRegister(offset + 2u)); + const u32 b3 = ZeroExtend32(g_cdrom.ReadRegister(offset + 3u)); + value = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + } + + case MemoryAccessSize::HalfWord: + { + const u32 lsb = ZeroExtend32(g_cdrom.ReadRegister(offset)); + const u32 msb = ZeroExtend32(g_cdrom.ReadRegister(offset + 1u)); + value = lsb | (msb << 8); + } + + case MemoryAccessSize::Byte: + default: + value = ZeroExtend32(g_cdrom.ReadRegister(offset)); + } + + return m_cdrom_access_time[static_cast(size)]; + } + else + { + switch (size) + { + case MemoryAccessSize::Word: + { + g_cdrom.WriteRegister(offset, Truncate8(value & 0xFFu)); + g_cdrom.WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu)); + g_cdrom.WriteRegister(offset + 2u, Truncate8((value >> 16) & 0xFFu)); + g_cdrom.WriteRegister(offset + 3u, Truncate8((value >> 24) & 0xFFu)); + } break; + + case MemoryAccessSize::HalfWord: + { + g_cdrom.WriteRegister(offset, Truncate8(value & 0xFFu)); + g_cdrom.WriteRegister(offset + 1u, Truncate8((value >> 8) & 0xFFu)); + } + break; + + case MemoryAccessSize::Byte: + default: + g_cdrom.WriteRegister(offset, Truncate8(value)); + break; + } + + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoGPUAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_gpu->ReadRegister(offset); + FixupUnalignedWordAccessW32(offset, value); + return 2; + } + else + { + FixupUnalignedWordAccessW32(offset, value); + g_gpu->WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoMDECAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_mdec.ReadRegister(offset); + FixupUnalignedWordAccessW32(offset, value); + return 2; + } + else + { + FixupUnalignedWordAccessW32(offset, value); + g_mdec.WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoAccessInterruptController(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_interrupt_controller.ReadRegister(offset); + FixupUnalignedWordAccessW32(offset, value); + return 2; + } + else + { + FixupUnalignedWordAccessW32(offset, value); + g_interrupt_controller.WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoAccessTimers(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = g_timers.ReadRegister(offset); + FixupUnalignedWordAccessW32(offset, value); + return 2; + } + else + { + FixupUnalignedWordAccessW32(offset, value); + g_timers.WriteRegister(offset, value); + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoAccessSPU(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + switch (size) + { + case MemoryAccessSize::Word: + { + // 32-bit reads are read as two 16-bit accesses. + const u16 lsb = g_spu.ReadRegister(offset); + const u16 msb = g_spu.ReadRegister(offset + 2); + value = ZeroExtend32(lsb) | (ZeroExtend32(msb) << 16); + } + break; + + case MemoryAccessSize::HalfWord: + { + value = ZeroExtend32(g_spu.ReadRegister(offset)); + } + break; + + case MemoryAccessSize::Byte: + default: + { + const u16 value16 = g_spu.ReadRegister(FIXUP_HALFWORD_READ_OFFSET(offset)); + value = FIXUP_HALFWORD_READ_VALUE(offset, value16); + } + break; + } + + return m_spu_access_time[static_cast(size)]; + } + else + { + // 32-bit writes are written as two 16-bit writes. + // TODO: Ignore if address is not aligned. + switch (size) + { + case MemoryAccessSize::Word: + { + DebugAssert(Common::IsAlignedPow2(offset, 2)); + g_spu.WriteRegister(offset, Truncate16(value)); + g_spu.WriteRegister(offset + 2, Truncate16(value >> 16)); + break; + } + + case MemoryAccessSize::HalfWord: + { + DebugAssert(Common::IsAlignedPow2(offset, 2)); + g_spu.WriteRegister(offset, Truncate16(value)); + break; + } + + case MemoryAccessSize::Byte: + { + g_spu.WriteRegister(FIXUP_HALFWORD_READ_OFFSET(offset), Truncate16(FIXUP_HALFWORD_READ_VALUE(offset, value))); + break; + } + } + + return 0; + } +} + +template +ALWAYS_INLINE static TickCount DoDMAAccess(u32 offset, u32& value) +{ + if constexpr (type == MemoryAccessType::Read) + { + value = FIXUP_WORD_READ_VALUE(offset, g_dma.ReadRegister(FIXUP_WORD_READ_OFFSET(offset))); + return 2; + } + else + { + switch (size) + { + case MemoryAccessSize::Byte: + case MemoryAccessSize::HalfWord: + { + // zero extend length register + if ((offset & u32(0xF0)) < 7 && (offset & u32(0x0F)) == 0x4) + value = ZeroExtend32(value); + else + FixupUnalignedWordAccessW32(offset, value); + } + + default: + break; + } + + g_dma.WriteRegister(offset, value); + return 0; + } +} + +} // namespace Bus + +namespace CPU { + +// defined in cpu_core.cpp +void RaiseException(Exception excode); +void RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE); + +static void WriteCacheControl(u32 value) +{ + Log_WarningPrintf("Cache control <- 0x%08X", value); + g_state.cache_control = value; +} + +template +ALWAYS_INLINE static TickCount DoScratchpadAccess(PhysicalMemoryAddress address, u32& value) +{ + const PhysicalMemoryAddress cache_offset = address & DCACHE_OFFSET_MASK; + if constexpr (size == MemoryAccessSize::Byte) + { + if constexpr (type == MemoryAccessType::Read) + value = ZeroExtend32(g_state.dcache[cache_offset]); + else + g_state.dcache[cache_offset] = Truncate8(value); + } + else if constexpr (size == MemoryAccessSize::HalfWord) + { + if constexpr (type == MemoryAccessType::Read) + { + u16 temp; + std::memcpy(&temp, &g_state.dcache[cache_offset], sizeof(temp)); + value = ZeroExtend32(temp); + } + else + { + u16 temp = Truncate16(value); + std::memcpy(&g_state.dcache[cache_offset], &temp, sizeof(temp)); + } + } + else if constexpr (size == MemoryAccessSize::Word) + { + if constexpr (type == MemoryAccessType::Read) + std::memcpy(&value, &g_state.dcache[cache_offset], sizeof(value)); + else + std::memcpy(&g_state.dcache[cache_offset], &value, sizeof(value)); } - m_dma->WriteRegister(offset, value); + return 0; } + +template +static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& value) +{ + using namespace Bus; + + switch (address >> 29) + { + case 0x00: // KUSEG 0M-512M + case 0x04: // KSEG0 - physical memory cached + { + if constexpr (type == MemoryAccessType::Write) + { + if (g_state.cop0_regs.sr.Isc) + return 0; + } + + address &= PHYSICAL_MEMORY_ADDRESS_MASK; + if ((address & DCACHE_LOCATION_MASK) == DCACHE_LOCATION) + return DoScratchpadAccess(address, value); + } + break; + + case 0x01: // KUSEG 512M-1024M + case 0x02: // KUSEG 1024M-1536M + case 0x03: // KUSEG 1536M-2048M + { + // Above 512mb raises an exception. + return -1; + } + + case 0x05: // KSEG1 - physical memory uncached + { + address &= PHYSICAL_MEMORY_ADDRESS_MASK; + } + break; + + case 0x06: // KSEG2 + case 0x07: // KSEG2 + { + if (address == 0xFFFE0130) + { + if constexpr (type == MemoryAccessType::Read) + value = g_state.cache_control; + else + WriteCacheControl(value); + + return 0; + } + else + { + return -1; + } + } + } + + if (address < 0x800000) + { + return DoRAMAccess(address, value); + } + else if (address < EXP1_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (EXP1_BASE + EXP1_SIZE)) + { + return DoEXP1Access(address & EXP1_MASK, value); + } + else if (address < MEMCTRL_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (MEMCTRL_BASE + MEMCTRL_SIZE)) + { + return DoMemoryControlAccess(address & MEMCTRL_MASK, value); + } + else if (address < (PAD_BASE + PAD_SIZE)) + { + return DoPadAccess(address & PAD_MASK, value); + } + else if (address < (SIO_BASE + SIO_SIZE)) + { + return DoSIOAccess(address & SIO_MASK, value); + } + else if (address < (MEMCTRL2_BASE + MEMCTRL2_SIZE)) + { + return DoMemoryControl2Access(address & MEMCTRL2_MASK, value); + } + else if (address < (INTERRUPT_CONTROLLER_BASE + INTERRUPT_CONTROLLER_SIZE)) + { + return DoAccessInterruptController(address & INTERRUPT_CONTROLLER_MASK, value); + } + else if (address < (DMA_BASE + DMA_SIZE)) + { + return DoDMAAccess(address & DMA_MASK, value); + } + else if (address < (TIMERS_BASE + TIMERS_SIZE)) + { + return DoAccessTimers(address & TIMERS_MASK, value); + } + else if (address < CDROM_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (CDROM_BASE + GPU_SIZE)) + { + return DoCDROMAccess(address & CDROM_MASK, value); + } + else if (address < (GPU_BASE + GPU_SIZE)) + { + return DoGPUAccess(address & GPU_MASK, value); + } + else if (address < (MDEC_BASE + MDEC_SIZE)) + { + return DoMDECAccess(address & MDEC_MASK, value); + } + else if (address < SPU_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (SPU_BASE + SPU_SIZE)) + { + return DoAccessSPU(address & SPU_MASK, value); + } + else if (address < EXP2_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (EXP2_BASE + EXP2_SIZE)) + { + return DoEXP2Access(address & EXP2_MASK, value); + } + else if (address < BIOS_BASE) + { + return DoInvalidAccess(type, size, address, value); + } + else if (address < (BIOS_BASE + BIOS_SIZE)) + { + return DoBIOSAccess(static_cast(address - BIOS_BASE), value); + } + else + { + return DoInvalidAccess(type, size, address, value); + } +} + +template +static bool DoAlignmentCheck(VirtualMemoryAddress address) +{ + if constexpr (size == MemoryAccessSize::HalfWord) + { + if (Common::IsAlignedPow2(address, 2)) + return true; + } + else if constexpr (size == MemoryAccessSize::Word) + { + if (Common::IsAlignedPow2(address, 4)) + return true; + } + else + { + return true; + } + + g_state.cop0_regs.BadVaddr = address; + RaiseException(type == MemoryAccessType::Read ? Exception::AdEL : Exception::AdES); + return false; +} + +bool FetchInstruction() +{ + DebugAssert(Common::IsAlignedPow2(g_state.regs.npc, 4)); + if (DoMemoryAccess(g_state.regs.npc, g_state.next_instruction.bits) < + 0) + { + // Bus errors don't set BadVaddr. + RaiseException(Exception::IBE, g_state.regs.npc, false, false, 0); + return false; + } + + g_state.regs.pc = g_state.regs.npc; + g_state.regs.npc += sizeof(g_state.next_instruction.bits); + return true; +} + +bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value) +{ + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(addr, temp); + *value = Truncate8(temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + g_state.pending_ticks += cycles; + return true; +} + +bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value) +{ + if (!DoAlignmentCheck(addr)) + return false; + + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(addr, temp); + *value = Truncate16(temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + g_state.pending_ticks += cycles; + return true; +} + +bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value) +{ + if (!DoAlignmentCheck(addr)) + return false; + + const TickCount cycles = DoMemoryAccess(addr, *value); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + g_state.pending_ticks += cycles; + return true; +} + +bool WriteMemoryByte(VirtualMemoryAddress addr, u8 value) +{ + u32 temp = ZeroExtend32(value); + const TickCount cycles = DoMemoryAccess(addr, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) +{ + if (!DoAlignmentCheck(addr)) + return false; + + u32 temp = ZeroExtend32(value); + const TickCount cycles = DoMemoryAccess(addr, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value) +{ + if (!DoAlignmentCheck(addr)) + return false; + + const TickCount cycles = DoMemoryAccess(addr, value); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value) +{ + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(addr, temp); + *value = Truncate8(temp); + return (cycles >= 0); +} + +bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value) +{ + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(addr, temp); + *value = Truncate16(temp); + return (cycles >= 0); +} + +bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value) +{ + return DoMemoryAccess(addr, *value) >= 0; +} + +bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value) +{ + u32 temp = ZeroExtend32(value); + return DoMemoryAccess(addr, temp) >= 0; +} + +bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) +{ + u32 temp = ZeroExtend32(value); + return DoMemoryAccess(addr, temp) >= 0; +} + +bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value) +{ + return DoMemoryAccess(addr, value) >= 0; +} + +namespace Recompiler::Thunks { + +u64 ReadMemoryByte(u32 pc, u32 address) +{ + g_state.current_instruction_pc = pc; + + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(address, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return UINT64_C(0xFFFFFFFFFFFFFFFF); + } + + g_state.pending_ticks += cycles; + return ZeroExtend64(temp); +} + +u64 ReadMemoryHalfWord(u32 pc, u32 address) +{ + g_state.current_instruction_pc = pc; + + if (!DoAlignmentCheck(address)) + return UINT64_C(0xFFFFFFFFFFFFFFFF); + + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(address, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return UINT64_C(0xFFFFFFFFFFFFFFFF); + } + + g_state.pending_ticks += cycles; + return ZeroExtend64(temp); +} + +u64 ReadMemoryWord(u32 pc, u32 address) +{ + g_state.current_instruction_pc = pc; + + if (!DoAlignmentCheck(address)) + return UINT64_C(0xFFFFFFFFFFFFFFFF); + + u32 temp = 0; + const TickCount cycles = DoMemoryAccess(address, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return UINT64_C(0xFFFFFFFFFFFFFFFF); + } + + g_state.pending_ticks += cycles; + return ZeroExtend64(temp); +} + +bool WriteMemoryByte(u32 pc, u32 address, u8 value) +{ + g_state.current_instruction_pc = pc; + + u32 temp = ZeroExtend32(value); + const TickCount cycles = DoMemoryAccess(address, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +bool WriteMemoryHalfWord(u32 pc, u32 address, u16 value) +{ + g_state.current_instruction_pc = pc; + + if (!DoAlignmentCheck(address)) + return false; + + u32 temp = ZeroExtend32(value); + const TickCount cycles = DoMemoryAccess(address, temp); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +bool WriteMemoryWord(u32 pc, u32 address, u32 value) +{ + g_state.current_instruction_pc = pc; + + if (!DoAlignmentCheck(address)) + return false; + + const TickCount cycles = DoMemoryAccess(address, value); + if (cycles < 0) + { + RaiseException(Exception::DBE); + return false; + } + + DebugAssert(cycles == 0); + return true; +} + +} // namespace Recompiler::Thunks + +} // namespace CPU diff --git a/src/core/bus.h b/src/core/bus.h index 6e126885a..a18ba1274 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -1,5 +1,6 @@ #pragma once #include "common/bitfield.h" +#include "cpu_code_cache.h" #include "types.h" #include #include @@ -8,288 +9,139 @@ class StateWrapper; -namespace CPU { -class Core; -class CodeCache; -} // namespace CPU +namespace Bus { -class DMA; -class InterruptController; -class GPU; -class CDROM; -class Pad; -class Timers; -class SPU; -class MDEC; -class SIO; -class System; - -class Bus +enum : u32 { - friend DMA; - -public: - enum : u32 - { - RAM_BASE = 0x00000000, - RAM_SIZE = 0x200000, - RAM_MASK = RAM_SIZE - 1, - RAM_MIRROR_END = 0x800000, - EXP1_BASE = 0x1F000000, - EXP1_SIZE = 0x800000, - EXP1_MASK = EXP1_SIZE - 1, - MEMCTRL_BASE = 0x1F801000, - MEMCTRL_SIZE = 0x40, - MEMCTRL_MASK = MEMCTRL_SIZE - 1, - PAD_BASE = 0x1F801040, - PAD_SIZE = 0x10, - PAD_MASK = PAD_SIZE - 1, - SIO_BASE = 0x1F801050, - SIO_SIZE = 0x10, - SIO_MASK = SIO_SIZE - 1, - MEMCTRL2_BASE = 0x1F801060, - MEMCTRL2_SIZE = 0x10, - MEMCTRL2_MASK = MEMCTRL_SIZE - 1, - INTERRUPT_CONTROLLER_BASE = 0x1F801070, - INTERRUPT_CONTROLLER_SIZE = 0x10, - INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1, - DMA_BASE = 0x1F801080, - DMA_SIZE = 0x80, - DMA_MASK = DMA_SIZE - 1, - TIMERS_BASE = 0x1F801100, - TIMERS_SIZE = 0x40, - TIMERS_MASK = TIMERS_SIZE - 1, - CDROM_BASE = 0x1F801800, - CDROM_SIZE = 0x10, - CDROM_MASK = CDROM_SIZE - 1, - GPU_BASE = 0x1F801810, - GPU_SIZE = 0x10, - GPU_MASK = GPU_SIZE - 1, - MDEC_BASE = 0x1F801820, - MDEC_SIZE = 0x10, - MDEC_MASK = MDEC_SIZE - 1, - SPU_BASE = 0x1F801C00, - SPU_SIZE = 0x400, - SPU_MASK = 0x3FF, - EXP2_BASE = 0x1F802000, - EXP2_SIZE = 0x2000, - EXP2_MASK = EXP2_SIZE - 1, - BIOS_BASE = 0x1FC00000, - BIOS_SIZE = 0x80000 - }; - - Bus(); - ~Bus(); - - void Initialize(CPU::Core* cpu, CPU::CodeCache* cpu_code_cache, DMA* dma, InterruptController* interrupt_controller, - GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers, SPU* spu, MDEC* mdec, SIO* sio); - void Reset(); - bool DoState(StateWrapper& sw); - - bool ReadByte(PhysicalMemoryAddress address, u8* value); - bool ReadHalfWord(PhysicalMemoryAddress address, u16* value); - bool ReadWord(PhysicalMemoryAddress address, u32* value); - bool WriteByte(PhysicalMemoryAddress address, u8 value); - bool WriteHalfWord(PhysicalMemoryAddress address, u16 value); - bool WriteWord(PhysicalMemoryAddress address, u32 value); - - template - TickCount DispatchAccess(PhysicalMemoryAddress address, u32& value); - - // Optimized variant for burst/multi-word read/writing. - TickCount ReadWords(PhysicalMemoryAddress address, u32* words, u32 word_count); - TickCount WriteWords(PhysicalMemoryAddress address, const u32* words, u32 word_count); - - void SetExpansionROM(std::vector data); - void SetBIOS(const std::vector& image); - - // changing interfaces - void SetGPU(GPU* gpu) { m_gpu = gpu; } - - /// Returns the address which should be used for code caching (i.e. removes mirrors). - ALWAYS_INLINE static PhysicalMemoryAddress UnmirrorAddress(PhysicalMemoryAddress address) - { - // RAM - if (address < 0x800000) - return address & UINT32_C(0x1FFFFF); - else - return address; - } - - /// Returns true if the address specified is cacheable (RAM or BIOS). - ALWAYS_INLINE static bool IsCacheableAddress(PhysicalMemoryAddress address) - { - return (address < RAM_MIRROR_END) || (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)); - } - - /// Returns true if the address specified is writable (RAM). - ALWAYS_INLINE static bool IsRAMAddress(PhysicalMemoryAddress address) { return address < RAM_MIRROR_END; } - - /// Flags a RAM region as code, so we know when to invalidate blocks. - ALWAYS_INLINE void SetRAMCodePage(u32 index) { m_ram_code_bits[index] = true; } - - /// Unflags a RAM region as code, the code cache will no longer be notified when writes occur. - ALWAYS_INLINE void ClearRAMCodePage(u32 index) { m_ram_code_bits[index] = false; } - - /// Clears all code bits for RAM regions. - ALWAYS_INLINE void ClearRAMCodePageFlags() { m_ram_code_bits.reset(); } - - /// Direct access to RAM - used by DMA. - ALWAYS_INLINE u8* GetRAM() { return m_ram; } - -private: - enum : u32 - { - MEMCTRL_REG_COUNT = 9 - }; - - union MEMDELAY - { - u32 bits; - - BitField access_time; // cycles - BitField use_com0_time; - BitField use_com1_time; - BitField use_com2_time; - BitField use_com3_time; - BitField data_bus_16bit; - BitField memory_window_size; - - static constexpr u32 WRITE_MASK = 0b10101111'00011111'11111111'11111111; - }; - - union COMDELAY - { - u32 bits; - - BitField com0; - BitField com1; - BitField com2; - BitField com3; - BitField comunk; - - static constexpr u32 WRITE_MASK = 0b00000000'00000011'11111111'11111111; - }; - - union MEMCTRL - { - u32 regs[MEMCTRL_REG_COUNT]; - - struct - { - u32 exp1_base; - u32 exp2_base; - MEMDELAY exp1_delay_size; - MEMDELAY exp3_delay_size; - MEMDELAY bios_delay_size; - MEMDELAY spu_delay_size; - MEMDELAY cdrom_delay_size; - MEMDELAY exp2_delay_size; - COMDELAY common_delay; - }; - }; - - static std::tuple CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay); - void RecalculateMemoryTimings(); - - template - TickCount DoRAMAccess(u32 offset, u32& value); - - template - TickCount DoBIOSAccess(u32 offset, u32& value); - - TickCount DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress address, u32& value); - - u32 DoReadEXP1(MemoryAccessSize size, u32 offset); - void DoWriteEXP1(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadEXP2(MemoryAccessSize size, u32 offset); - void DoWriteEXP2(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadMemoryControl(MemoryAccessSize size, u32 offset); - void DoWriteMemoryControl(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadMemoryControl2(MemoryAccessSize size, u32 offset); - void DoWriteMemoryControl2(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadPad(MemoryAccessSize size, u32 offset); - void DoWritePad(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadSIO(MemoryAccessSize size, u32 offset); - void DoWriteSIO(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadCDROM(MemoryAccessSize size, u32 offset); - void DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadGPU(MemoryAccessSize size, u32 offset); - void DoWriteGPU(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadMDEC(MemoryAccessSize size, u32 offset); - void DoWriteMDEC(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadInterruptController(MemoryAccessSize size, u32 offset); - void DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadDMA(MemoryAccessSize size, u32 offset); - void DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadTimers(MemoryAccessSize size, u32 offset); - void DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value); - - u32 DoReadSPU(MemoryAccessSize size, u32 offset); - void DoWriteSPU(MemoryAccessSize size, u32 offset, u32 value); - - void DoInvalidateCodeCache(u32 page_index); - - /// Returns the number of cycles stolen by DMA RAM access. - ALWAYS_INLINE static TickCount GetDMARAMTickCount(u32 word_count) - { - // DMA is using DRAM Hyper Page mode, allowing it to access DRAM rows at 1 clock cycle per word (effectively around - // 17 clks per 16 words, due to required row address loading, probably plus some further minimal overload due to - // refresh cycles). This is making DMA much faster than CPU memory accesses (CPU DRAM access takes 1 opcode cycle - // plus 6 waitstates, ie. 7 cycles in total). - return static_cast(word_count + ((word_count + 15) / 16)); - } - - /// Invalidates any code pages which overlap the specified range. - ALWAYS_INLINE void InvalidateCodePages(PhysicalMemoryAddress address, u32 word_count) - { - const u32 start_page = address / CPU_CODE_CACHE_PAGE_SIZE; - const u32 end_page = (address + word_count * sizeof(u32)) / CPU_CODE_CACHE_PAGE_SIZE; - for (u32 page = start_page; page <= end_page; page++) - { - if (m_ram_code_bits[page]) - DoInvalidateCodeCache(page); - } - } - - CPU::Core* m_cpu = nullptr; - CPU::CodeCache* m_cpu_code_cache = nullptr; - DMA* m_dma = nullptr; - InterruptController* m_interrupt_controller = nullptr; - GPU* m_gpu = nullptr; - CDROM* m_cdrom = nullptr; - Pad* m_pad = nullptr; - Timers* m_timers = nullptr; - SPU* m_spu = nullptr; - MDEC* m_mdec = nullptr; - SIO* m_sio = nullptr; - - std::array m_exp1_access_time = {}; - std::array m_exp2_access_time = {}; - std::array m_bios_access_time = {}; - std::array m_cdrom_access_time = {}; - std::array m_spu_access_time = {}; - - std::bitset m_ram_code_bits{}; - u8 m_ram[RAM_SIZE]{}; // 2MB RAM - u8 m_bios[BIOS_SIZE]{}; // 512K BIOS ROM - std::vector m_exp1_rom; - - MEMCTRL m_MEMCTRL = {}; - u32 m_ram_size_reg = 0; - - std::string m_tty_line_buffer; + RAM_BASE = 0x00000000, + RAM_SIZE = 0x200000, + RAM_MASK = RAM_SIZE - 1, + RAM_MIRROR_END = 0x800000, + EXP1_BASE = 0x1F000000, + EXP1_SIZE = 0x800000, + EXP1_MASK = EXP1_SIZE - 1, + MEMCTRL_BASE = 0x1F801000, + MEMCTRL_SIZE = 0x40, + MEMCTRL_MASK = MEMCTRL_SIZE - 1, + PAD_BASE = 0x1F801040, + PAD_SIZE = 0x10, + PAD_MASK = PAD_SIZE - 1, + SIO_BASE = 0x1F801050, + SIO_SIZE = 0x10, + SIO_MASK = SIO_SIZE - 1, + MEMCTRL2_BASE = 0x1F801060, + MEMCTRL2_SIZE = 0x10, + MEMCTRL2_MASK = MEMCTRL2_SIZE - 1, + INTERRUPT_CONTROLLER_BASE = 0x1F801070, + INTERRUPT_CONTROLLER_SIZE = 0x10, + INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1, + DMA_BASE = 0x1F801080, + DMA_SIZE = 0x80, + DMA_MASK = DMA_SIZE - 1, + TIMERS_BASE = 0x1F801100, + TIMERS_SIZE = 0x40, + TIMERS_MASK = TIMERS_SIZE - 1, + CDROM_BASE = 0x1F801800, + CDROM_SIZE = 0x10, + CDROM_MASK = CDROM_SIZE - 1, + GPU_BASE = 0x1F801810, + GPU_SIZE = 0x10, + GPU_MASK = GPU_SIZE - 1, + MDEC_BASE = 0x1F801820, + MDEC_SIZE = 0x10, + MDEC_MASK = MDEC_SIZE - 1, + SPU_BASE = 0x1F801C00, + SPU_SIZE = 0x400, + SPU_MASK = 0x3FF, + EXP2_BASE = 0x1F802000, + EXP2_SIZE = 0x2000, + EXP2_MASK = EXP2_SIZE - 1, + BIOS_BASE = 0x1FC00000, + BIOS_SIZE = 0x80000, + BIOS_MASK = 0x7FFFF, }; -#include "bus.inl" +enum : u32 +{ + MEMCTRL_REG_COUNT = 9 +}; + +void Initialize(); +void Shutdown(); +void Reset(); +bool DoState(StateWrapper& sw); + +void SetExpansionROM(std::vector data); +void SetBIOS(const std::vector& image); + +extern std::bitset m_ram_code_bits; +extern u8 g_ram[RAM_SIZE]; // 2MB RAM +extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM + +/// Returns the address which should be used for code caching (i.e. removes mirrors). +ALWAYS_INLINE PhysicalMemoryAddress UnmirrorAddress(PhysicalMemoryAddress address) +{ + // RAM + if (address < 0x800000) + return address & UINT32_C(0x1FFFFF); + else + return address; +} + +/// Returns true if the address specified is cacheable (RAM or BIOS). +ALWAYS_INLINE bool IsCacheableAddress(PhysicalMemoryAddress address) +{ + return (address < RAM_MIRROR_END) || (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE)); +} + +/// Reads a cachable address (RAM or BIOS). +ALWAYS_INLINE u32 ReadCacheableAddress(PhysicalMemoryAddress address) +{ + u32 value; + if (address < RAM_MIRROR_END) + { + std::memcpy(&value, &g_ram[address & RAM_MASK], sizeof(value)); + return value; + } + else + { + std::memcpy(&value, &g_bios[address & BIOS_MASK], sizeof(value)); + return value; + } +} + +/// Returns true if the address specified is writable (RAM). +ALWAYS_INLINE bool IsRAMAddress(PhysicalMemoryAddress address) { return address < RAM_MIRROR_END; } + +/// Flags a RAM region as code, so we know when to invalidate blocks. +ALWAYS_INLINE void SetRAMCodePage(u32 index) { m_ram_code_bits[index] = true; } + +/// Unflags a RAM region as code, the code cache will no longer be notified when writes occur. +ALWAYS_INLINE void ClearRAMCodePage(u32 index) { m_ram_code_bits[index] = false; } + +/// Clears all code bits for RAM regions. +ALWAYS_INLINE void ClearRAMCodePageFlags() { m_ram_code_bits.reset(); } + +/// Returns the number of cycles stolen by DMA RAM access. +ALWAYS_INLINE TickCount GetDMARAMTickCount(u32 word_count) +{ + // DMA is using DRAM Hyper Page mode, allowing it to access DRAM rows at 1 clock cycle per word (effectively around + // 17 clks per 16 words, due to required row address loading, probably plus some further minimal overload due to + // refresh cycles). This is making DMA much faster than CPU memory accesses (CPU DRAM access takes 1 opcode cycle + // plus 6 waitstates, ie. 7 cycles in total). + return static_cast(word_count + ((word_count + 15) / 16)); +} + +/// Invalidates any code pages which overlap the specified range. +ALWAYS_INLINE void InvalidateCodePages(PhysicalMemoryAddress address, u32 word_count) +{ + const u32 start_page = address / CPU_CODE_CACHE_PAGE_SIZE; + const u32 end_page = (address + word_count * sizeof(u32)) / CPU_CODE_CACHE_PAGE_SIZE; + for (u32 page = start_page; page <= end_page; page++) + { + if (m_ram_code_bits[page]) + CPU::CodeCache::InvalidateBlocksWithPageIndex(page); + } +} + +} // namespace Bus diff --git a/src/core/bus.inl b/src/core/bus.inl deleted file mode 100644 index 3d8334e9f..000000000 --- a/src/core/bus.inl +++ /dev/null @@ -1,288 +0,0 @@ -#pragma once -#include "bus.h" - -template -TickCount Bus::DoRAMAccess(u32 offset, u32& value) -{ - // TODO: Configurable mirroring. - offset &= UINT32_C(0x1FFFFF); - if constexpr (type == MemoryAccessType::Read) - { - if constexpr (size == MemoryAccessSize::Byte) - { - value = ZeroExtend32(m_ram[offset]); - } - else if constexpr (size == MemoryAccessSize::HalfWord) - { - u16 temp; - std::memcpy(&temp, &m_ram[offset], sizeof(u16)); - value = ZeroExtend32(temp); - } - else if constexpr (size == MemoryAccessSize::Word) - { - std::memcpy(&value, &m_ram[offset], sizeof(u32)); - } - } - else - { - const u32 page_index = offset / CPU_CODE_CACHE_PAGE_SIZE; - if (m_ram_code_bits[page_index]) - DoInvalidateCodeCache(page_index); - - if constexpr (size == MemoryAccessSize::Byte) - { - m_ram[offset] = Truncate8(value); - } - else if constexpr (size == MemoryAccessSize::HalfWord) - { - const u16 temp = Truncate16(value); - std::memcpy(&m_ram[offset], &temp, sizeof(u16)); - } - else if constexpr (size == MemoryAccessSize::Word) - { - std::memcpy(&m_ram[offset], &value, sizeof(u32)); - } - } - - return (type == MemoryAccessType::Read) ? 4 : 0; -} - -template -TickCount Bus::DoBIOSAccess(u32 offset, u32& value) -{ - // TODO: Configurable mirroring. - if constexpr (type == MemoryAccessType::Read) - { - offset &= UINT32_C(0x7FFFF); - if constexpr (size == MemoryAccessSize::Byte) - { - value = ZeroExtend32(m_bios[offset]); - } - else if constexpr (size == MemoryAccessSize::HalfWord) - { - u16 temp; - std::memcpy(&temp, &m_bios[offset], sizeof(u16)); - value = ZeroExtend32(temp); - } - else - { - std::memcpy(&value, &m_bios[offset], sizeof(u32)); - } - } - else - { - // Writes are ignored. - } - - return m_bios_access_time[static_cast(size)]; -} - -template -TickCount Bus::DispatchAccess(PhysicalMemoryAddress address, u32& value) -{ - if (address < 0x800000) - { - return DoRAMAccess(address, value); - } - else if (address < EXP1_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (EXP1_BASE + EXP1_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadEXP1(size, address & EXP1_MASK); - return m_exp1_access_time[static_cast(size)]; - } - else - { - DoWriteEXP1(size, address & EXP1_MASK, value); - return 0; - } - } - else if (address < MEMCTRL_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (MEMCTRL_BASE + MEMCTRL_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadMemoryControl(size, address & PAD_MASK); - return 2; - } - else - { - DoWriteMemoryControl(size, address & PAD_MASK, value); - return 0; - } - } - else if (address < (PAD_BASE + PAD_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadPad(size, address & PAD_MASK); - return 2; - } - else - { - DoWritePad(size, address & PAD_MASK, value); - return 0; - } - } - else if (address < (SIO_BASE + SIO_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadSIO(size, address & SIO_MASK); - return 2; - } - else - { - DoWriteSIO(size, address & SIO_MASK, value); - return 0; - } - } - else if (address < (MEMCTRL2_BASE + MEMCTRL2_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadMemoryControl2(size, address & PAD_MASK); - return 2; - } - else - { - DoWriteMemoryControl2(size, address & PAD_MASK, value); - return 0; - } - } - else if (address < (INTERRUPT_CONTROLLER_BASE + INTERRUPT_CONTROLLER_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadInterruptController(size, address & INTERRUPT_CONTROLLER_MASK); - return 2; - } - else - { - DoWriteInterruptController(size, address & INTERRUPT_CONTROLLER_MASK, value); - return 0; - } - } - else if (address < (DMA_BASE + DMA_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadDMA(size, address & DMA_MASK); - return 2; - } - else - { - DoWriteDMA(size, address & DMA_MASK, value); - return 0; - } - } - else if (address < (TIMERS_BASE + TIMERS_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadTimers(size, address & TIMERS_MASK); - return 2; - } - else - { - DoWriteTimers(size, address & TIMERS_MASK, value); - return 0; - } - } - else if (address < CDROM_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (CDROM_BASE + GPU_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadCDROM(size, address & CDROM_MASK); - return m_cdrom_access_time[static_cast(size)]; - } - else - { - DoWriteCDROM(size, address & CDROM_MASK, value); - return 0; - } - } - else if (address < (GPU_BASE + GPU_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadGPU(size, address & GPU_MASK); - return 2; - } - else - { - DoWriteGPU(size, address & GPU_MASK, value); - return 0; - } - } - else if (address < (MDEC_BASE + MDEC_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadMDEC(size, address & MDEC_MASK); - return 2; - } - else - { - DoWriteMDEC(size, address & MDEC_MASK, value); - return 0; - } - } - else if (address < SPU_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (SPU_BASE + SPU_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadSPU(size, address & SPU_MASK); - return m_spu_access_time[static_cast(size)]; - } - else - { - DoWriteSPU(size, address & SPU_MASK, value); - return 0; - } - } - else if (address < EXP2_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (EXP2_BASE + EXP2_SIZE)) - { - if constexpr (type == MemoryAccessType::Read) - { - value = DoReadEXP2(size, address & EXP2_MASK); - return m_exp2_access_time[static_cast(size)]; - } - else - { - DoWriteEXP2(size, address & EXP2_MASK, value); - return 0; - } - } - else if (address < BIOS_BASE) - { - return DoInvalidAccess(type, size, address, value); - } - else if (address < (BIOS_BASE + BIOS_SIZE)) - { - return DoBIOSAccess(static_cast(address - BIOS_BASE), value); - } - else - { - return DoInvalidAccess(type, size, address, value); - } -} diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 1eecb67dd..99cf1a3be 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -275,23 +275,31 @@ static std::array s_command_info = {{ 0 // Unknown }}; +CDROM g_cdrom; + CDROM::CDROM() = default; CDROM::~CDROM() = default; -void CDROM::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, SPU* spu) +void CDROM::Initialize() { - m_system = system; - m_dma = dma; - m_interrupt_controller = interrupt_controller; - m_spu = spu; m_command_event = - m_system->CreateTimingEvent("CDROM Command Event", 1, 1, std::bind(&CDROM::ExecuteCommand, this), false); - m_drive_event = m_system->CreateTimingEvent("CDROM Drive Event", 1, 1, - std::bind(&CDROM::ExecuteDrive, this, std::placeholders::_2), false); + TimingEvents::CreateTimingEvent("CDROM Command Event", 1, 1, std::bind(&CDROM::ExecuteCommand, this), false); + m_drive_event = TimingEvents::CreateTimingEvent("CDROM Drive Event", 1, 1, + std::bind(&CDROM::ExecuteDrive, this, std::placeholders::_2), false); - if (m_system->GetSettings().cdrom_read_thread) + if (g_settings.cdrom_read_thread) m_reader.StartThread(); + + Reset(); +} + +void CDROM::Shutdown() +{ + m_command_event.reset(); + m_drive_event.reset(); + m_reader.StopThread(); + m_reader.RemoveMedia(); } void CDROM::Reset() @@ -438,7 +446,7 @@ void CDROM::InsertMedia(std::unique_ptr media) // set the region from the system area of the disc m_disc_region = GameList::GetRegionForImage(media.get()); Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", Settings::GetDiscRegionName(m_disc_region), - Settings::GetConsoleRegionName(m_system->GetRegion())); + Settings::GetConsoleRegionName(System::GetRegion())); // motor automatically spins up if (m_drive_state != DriveState::ShellOpening) @@ -787,7 +795,7 @@ void CDROM::UpdateStatusRegister() m_status.DRQSTS = !m_data_fifo.IsEmpty(); m_status.BUSYSTS = HasPendingCommand(); - m_dma->SetRequest(DMA::Channel::CDROM, m_status.DRQSTS); + g_dma.SetRequest(DMA::Channel::CDROM, m_status.DRQSTS); } void CDROM::UpdateInterruptRequest() @@ -795,7 +803,7 @@ void CDROM::UpdateInterruptRequest() if ((m_interrupt_flag_register & m_interrupt_enable_register) == 0) return; - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::CDROM); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::CDROM); } TickCount CDROM::GetAckDelayForCommand(Command command) @@ -1421,7 +1429,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand) { Log_DebugPrintf("Get CDROM region ID string"); - switch (m_system->GetRegion()) + switch (System::GetRegion()) { case ConsoleRegion::NTSC_J: { @@ -1860,9 +1868,9 @@ void CDROM::DoIDRead() m_current_lba = 0; m_reader.QueueReadSector(0); - if (m_system->GetSettings().cdrom_region_check && + if (g_settings.cdrom_region_check && (m_disc_region == DiscRegion::Other || - m_system->GetRegion() != System::GetConsoleRegionForDiscRegion(m_disc_region))) + System::GetRegion() != System::GetConsoleRegionForDiscRegion(m_disc_region))) { stat_byte |= STAT_ID_ERROR; flags_byte |= (1 << 7); // Unlicensed @@ -2214,7 +2222,7 @@ void CDROM::ProcessXAADPCMSector(const u8* raw_sector, const CDImage::SubChannel if (m_muted || m_adpcm_muted) return; - m_spu->GeneratePendingSamples(); + g_spu.GeneratePendingSamples(); if (m_last_sector_subheader.codinginfo.IsStereo()) { @@ -2282,7 +2290,7 @@ void CDROM::ProcessCDDASector(const u8* raw_sector, const CDImage::SubChannelQ& if (m_muted) return; - m_spu->GeneratePendingSamples(); + g_spu.GeneratePendingSamples(); constexpr bool is_stereo = true; constexpr u32 num_samples = CDImage::RAW_SECTOR_SIZE / sizeof(s16) / (is_stereo ? 2 : 1); @@ -2347,7 +2355,7 @@ void CDROM::DrawDebugWindow() const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 550.0f * framebuffer_scale), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("CDROM State", &m_system->GetSettings().debugging.show_cdrom_state)) + if (!ImGui::Begin("CDROM State", &g_settings.debugging.show_cdrom_state)) { ImGui::End(); return; diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 840d76b06..4311f900d 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -12,20 +12,16 @@ #include class StateWrapper; - -class System; class TimingEvent; -class DMA; -class InterruptController; -class SPU; -class CDROM +class CDROM final { public: CDROM(); ~CDROM(); - void Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, SPU* spu); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -276,10 +272,6 @@ private: template void ResampleXAADPCM(const s16* frames_in, u32 num_frames_in); - System* m_system = nullptr; - DMA* m_dma = nullptr; - InterruptController* m_interrupt_controller = nullptr; - SPU* m_spu = nullptr; std::unique_ptr m_command_event; std::unique_ptr m_drive_event; @@ -347,5 +339,7 @@ private: CDROMAsyncReader m_reader; // two 16-bit samples packed in 32-bits - InlineFIFOQueue m_audio_fifo; + HeapFIFOQueue m_audio_fifo; }; + +extern CDROM g_cdrom; diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 4f829fb69..b83d66ac7 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -39,14 +39,14 @@ float Controller::GetVibrationMotorStrength(u32 motor) return 0.0f; } -void Controller::LoadSettings(HostInterface* host_interface, const char* section) {} +void Controller::LoadSettings(const char* section) {} bool Controller::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) { return false; } -std::unique_ptr Controller::Create(System* system, ControllerType type, u32 index) +std::unique_ptr Controller::Create(ControllerType type, u32 index) { switch (type) { @@ -54,13 +54,13 @@ std::unique_ptr Controller::Create(System* system, ControllerType ty return DigitalController::Create(); case ControllerType::AnalogController: - return AnalogController::Create(system, index); + return AnalogController::Create(index); case ControllerType::NamcoGunCon: - return NamcoGunCon::Create(system); + return NamcoGunCon::Create(); case ControllerType::PlayStationMouse: - return PlayStationMouse::Create(system); + return PlayStationMouse::Create(); case ControllerType::NeGcon: return NeGcon::Create(); diff --git a/src/core/controller.h b/src/core/controller.h index 2c8913460..3c63c3900 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -9,7 +9,6 @@ #include class StateWrapper; -class System; class HostInterface; class Controller @@ -53,13 +52,13 @@ public: virtual float GetVibrationMotorStrength(u32 motor); /// Loads/refreshes any per-controller settings. - virtual void LoadSettings(HostInterface* host_interface, const char* section); + virtual void LoadSettings(const char* section); /// Returns the software cursor to use for this controller, if any. virtual bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale); /// Creates a new controller of the specified type. - static std::unique_ptr Create(System* system, ControllerType type, u32 index); + static std::unique_ptr Create(ControllerType type, u32 index); /// Gets the integer code for an axis in the specified controller type. static std::optional GetAxisCodeByName(ControllerType type, std::string_view axis_name); diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 79141511b..0d621ecb4 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -57,7 +57,6 @@ - @@ -150,11 +149,6 @@ {ee054e08-3799-4a59-a422-18259c105ffd} - - - - - {868B98C8-65A1-494B-8346-250A73A48C0A} Win32Proj diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index b9d17af4c..9261a37ae 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -27,7 +27,6 @@ - @@ -64,7 +63,6 @@ - @@ -95,10 +93,6 @@ - - - - - + \ No newline at end of file diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index 1a979d4ae..55de56bb2 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -1,187 +1,229 @@ #include "cpu_code_cache.h" +#include "bus.h" #include "common/log.h" #include "cpu_core.h" #include "cpu_disasm.h" #include "system.h" +#include "timing_event.h" Log_SetChannel(CPU::CodeCache); #ifdef WITH_RECOMPILER #include "cpu_recompiler_code_generator.h" -#include "cpu_recompiler_thunks.h" #endif -namespace CPU { +namespace CPU::CodeCache { constexpr bool USE_BLOCK_LINKING = true; +#ifdef WITH_RECOMPILER static constexpr u32 RECOMPILER_CODE_CACHE_SIZE = 32 * 1024 * 1024; static constexpr u32 RECOMPILER_FAR_CODE_CACHE_SIZE = 32 * 1024 * 1024; +static constexpr u32 RECOMPILER_GUARD_SIZE = 4096; +alignas(Recompiler::CODE_STORAGE_ALIGNMENT) static u8 + s_code_storage[RECOMPILER_CODE_CACHE_SIZE + RECOMPILER_FAR_CODE_CACHE_SIZE]; +static JitCodeBuffer s_code_buffer; +#endif -CodeCache::CodeCache() = default; +using BlockMap = std::unordered_map; -CodeCache::~CodeCache() +void LogCurrentState(); + +/// Returns the block key for the current execution state. +static CodeBlockKey GetNextBlockKey(); + +/// Looks up the block in the cache if it's already been compiled. +static CodeBlock* LookupBlock(CodeBlockKey key); + +/// Can the current block execute? This will re-validate the block if necessary. +/// The block can also be flushed if recompilation failed, so ignore the pointer if false is returned. +static bool RevalidateBlock(CodeBlock* block); + +static bool CompileBlock(CodeBlock* block); +static void FlushBlock(CodeBlock* block); +static void AddBlockToPageMap(CodeBlock* block); +static void RemoveBlockFromPageMap(CodeBlock* block); + +/// Link block from to to. +static void LinkBlock(CodeBlock* from, CodeBlock* to); + +/// Unlink all blocks which point to this block, and any that this block links to. +static void UnlinkBlock(CodeBlock* block); + +static bool s_use_recompiler = false; +static BlockMap s_blocks; +static std::array, CPU_CODE_CACHE_PAGE_COUNT> m_ram_block_map; + +void Initialize(bool use_recompiler) { - if (m_system) - Flush(); -} - -void CodeCache::Initialize(System* system, Core* core, Bus* bus, bool use_recompiler) -{ - m_system = system; - m_core = core; - m_bus = bus; + Assert(s_blocks.empty()); #ifdef WITH_RECOMPILER - m_use_recompiler = use_recompiler; - m_code_buffer = std::make_unique(RECOMPILER_CODE_CACHE_SIZE, RECOMPILER_FAR_CODE_CACHE_SIZE); - m_asm_functions = std::make_unique(); - m_asm_functions->Generate(m_code_buffer.get()); + s_use_recompiler = use_recompiler; + if (!s_code_buffer.Initialize(s_code_storage, sizeof(s_code_storage), RECOMPILER_FAR_CODE_CACHE_SIZE, + RECOMPILER_GUARD_SIZE)) + { + Panic("Failed to initialize code space"); + } #else - m_use_recompiler = false; + s_use_recompiler = false; #endif } -void CodeCache::Execute() +void Shutdown() { - CodeBlockKey next_block_key = GetNextBlockKey(); + Flush(); + s_code_buffer.Destroy(); +} - while (m_core->m_pending_ticks < m_core->m_downcount) +void Execute() +{ + CodeBlockKey next_block_key; + + g_state.frame_done = false; + while (!g_state.frame_done) { - if (m_core->HasPendingInterrupt()) - { - // TODO: Fill in m_next_instruction... - m_core->SafeReadMemoryWord(m_core->m_regs.pc, &m_core->m_next_instruction.bits); - m_core->DispatchInterrupt(); - next_block_key = GetNextBlockKey(); - } - - CodeBlock* block = LookupBlock(next_block_key); - if (!block) - { - Log_WarningPrintf("Falling back to uncached interpreter at 0x%08X", m_core->GetRegs().pc); - InterpretUncachedBlock(); - continue; - } - - reexecute_block: - -#if 0 - const u32 tick = m_system->GetGlobalTickCounter() + m_core->GetPendingTicks(); - if (tick == 61033207) - __debugbreak(); -#endif - -#if 0 - LogCurrentState(); -#endif - - if (m_use_recompiler) - block->host_code(m_core); - else - InterpretCachedBlock(*block); - - if (m_core->m_pending_ticks >= m_core->m_downcount) - break; - else if (m_core->HasPendingInterrupt() || !USE_BLOCK_LINKING) - continue; + TimingEvents::UpdateCPUDowncount(); next_block_key = GetNextBlockKey(); - if (next_block_key.bits == block->key.bits) + while (g_state.pending_ticks < g_state.downcount) { - // we can jump straight to it if there's no pending interrupts - // ensure it's not a self-modifying block - if (!block->invalidated || RevalidateBlock(block)) - goto reexecute_block; - } - else if (!block->invalidated) - { - // Try to find an already-linked block. - // TODO: Don't need to dereference the block, just store a pointer to the code. - for (CodeBlock* linked_block : block->link_successors) + if (HasPendingInterrupt()) { - if (linked_block->key.bits == next_block_key.bits) - { - if (linked_block->invalidated && !RevalidateBlock(linked_block)) - { - // CanExecuteBlock can result in a block flush, so stop iterating here. - break; - } + // TODO: Fill in m_next_instruction... + SafeReadMemoryWord(g_state.regs.pc, &g_state.next_instruction.bits); + DispatchInterrupt(); + next_block_key = GetNextBlockKey(); + } - // Execute the linked block - block = linked_block; + CodeBlock* block = LookupBlock(next_block_key); + if (!block) + { + Log_WarningPrintf("Falling back to uncached interpreter at 0x%08X", g_state.regs.pc); + InterpretUncachedBlock(); + continue; + } + + reexecute_block: + +#if 0 + const u32 tick = g_system->GetGlobalTickCounter() + m_core->GetPendingTicks(); + if (tick == 61033207) + __debugbreak(); +#endif + +#if 0 + LogCurrentState(); +#endif + + if (s_use_recompiler) + block->host_code(); + else + InterpretCachedBlock(*block); + + if (g_state.pending_ticks >= g_state.downcount) + break; + else if (HasPendingInterrupt() || !USE_BLOCK_LINKING) + continue; + + next_block_key = GetNextBlockKey(); + if (next_block_key.bits == block->key.bits) + { + // we can jump straight to it if there's no pending interrupts + // ensure it's not a self-modifying block + if (!block->invalidated || RevalidateBlock(block)) + goto reexecute_block; + } + else if (!block->invalidated) + { + // Try to find an already-linked block. + // TODO: Don't need to dereference the block, just store a pointer to the code. + for (CodeBlock* linked_block : block->link_successors) + { + if (linked_block->key.bits == next_block_key.bits) + { + if (linked_block->invalidated && !RevalidateBlock(linked_block)) + { + // CanExecuteBlock can result in a block flush, so stop iterating here. + break; + } + + // Execute the linked block + block = linked_block; + goto reexecute_block; + } + } + + // No acceptable blocks found in the successor list, try a new one. + CodeBlock* next_block = LookupBlock(next_block_key); + if (next_block) + { + // Link the previous block to this new block if we find a new block. + LinkBlock(block, next_block); + block = next_block; goto reexecute_block; } } - - // No acceptable blocks found in the successor list, try a new one. - CodeBlock* next_block = LookupBlock(next_block_key); - if (next_block) - { - // Link the previous block to this new block if we find a new block. - LinkBlock(block, next_block); - block = next_block; - goto reexecute_block; - } } + + TimingEvents::RunEvents(); } // in case we switch to interpreter... - m_core->m_regs.npc = m_core->m_regs.pc; + g_state.regs.npc = g_state.regs.pc; } -void CodeCache::SetUseRecompiler(bool enable) +void SetUseRecompiler(bool enable) { #ifdef WITH_RECOMPILER - if (m_use_recompiler == enable) + if (s_use_recompiler == enable) return; - m_use_recompiler = enable; + s_use_recompiler = enable; Flush(); #endif } -void CodeCache::Flush() +void Flush() { - m_bus->ClearRAMCodePageFlags(); + Bus::ClearRAMCodePageFlags(); for (auto& it : m_ram_block_map) it.clear(); - for (const auto& it : m_blocks) + for (const auto& it : s_blocks) delete it.second; - m_blocks.clear(); + s_blocks.clear(); #ifdef WITH_RECOMPILER - m_code_buffer->Reset(); + s_code_buffer.Reset(); #endif } -void CodeCache::LogCurrentState() +void LogCurrentState() { - const auto& regs = m_core->m_regs; + const auto& regs = g_state.regs; WriteToExecutionLog("tick=%u pc=%08X zero=%08X at=%08X v0=%08X v1=%08X a0=%08X a1=%08X a2=%08X a3=%08X t0=%08X " "t1=%08X t2=%08X t3=%08X t4=%08X t5=%08X t6=%08X t7=%08X s0=%08X s1=%08X s2=%08X s3=%08X s4=%08X " "s5=%08X s6=%08X s7=%08X t8=%08X t9=%08X k0=%08X k1=%08X gp=%08X sp=%08X fp=%08X ra=%08X ldr=%s " "ldv=%08X\n", - m_system->GetGlobalTickCounter() + m_core->GetPendingTicks(), regs.pc, regs.zero, regs.at, - regs.v0, regs.v1, regs.a0, regs.a1, regs.a2, regs.a3, regs.t0, regs.t1, regs.t2, regs.t3, regs.t4, - regs.t5, regs.t6, regs.t7, regs.s0, regs.s1, regs.s2, regs.s3, regs.s4, regs.s5, regs.s6, regs.s7, - regs.t8, regs.t9, regs.k0, regs.k1, regs.gp, regs.sp, regs.fp, regs.ra, - (m_core->m_next_load_delay_reg == Reg::count) ? "NONE" : - GetRegName(m_core->m_next_load_delay_reg), - (m_core->m_next_load_delay_reg == Reg::count) ? 0 : m_core->m_next_load_delay_value); + TimingEvents::GetGlobalTickCounter() + GetPendingTicks(), regs.pc, regs.zero, regs.at, regs.v0, + regs.v1, regs.a0, regs.a1, regs.a2, regs.a3, regs.t0, regs.t1, regs.t2, regs.t3, regs.t4, regs.t5, + regs.t6, regs.t7, regs.s0, regs.s1, regs.s2, regs.s3, regs.s4, regs.s5, regs.s6, regs.s7, regs.t8, + regs.t9, regs.k0, regs.k1, regs.gp, regs.sp, regs.fp, regs.ra, + (g_state.next_load_delay_reg == Reg::count) ? "NONE" : GetRegName(g_state.next_load_delay_reg), + (g_state.next_load_delay_reg == Reg::count) ? 0 : g_state.next_load_delay_value); } -CodeBlockKey CodeCache::GetNextBlockKey() const +CodeBlockKey GetNextBlockKey() { CodeBlockKey key = {}; - key.SetPC(m_core->GetRegs().pc); - key.user_mode = m_core->InUserMode(); + key.SetPC(g_state.regs.pc); + key.user_mode = InUserMode(); return key; } -CodeBlock* CodeCache::LookupBlock(CodeBlockKey key) +CodeBlock* LookupBlock(CodeBlockKey key) { - BlockMap::iterator iter = m_blocks.find(key.bits); - if (iter != m_blocks.end()) + BlockMap::iterator iter = s_blocks.find(key.bits); + if (iter != s_blocks.end()) { // ensure it hasn't been invalidated CodeBlock* existing_block = iter->second; @@ -202,17 +244,15 @@ CodeBlock* CodeCache::LookupBlock(CodeBlockKey key) block = nullptr; } - iter = m_blocks.emplace(key.bits, block).first; + iter = s_blocks.emplace(key.bits, block).first; return block; } -bool CodeCache::RevalidateBlock(CodeBlock* block) +bool RevalidateBlock(CodeBlock* block) { for (const CodeBlockInstruction& cbi : block->instructions) { - u32 new_code = 0; - m_bus->DispatchAccess(cbi.pc & PHYSICAL_MEMORY_ADDRESS_MASK, - new_code); + u32 new_code = Bus::ReadCacheableAddress(cbi.pc & PHYSICAL_MEMORY_ADDRESS_MASK); if (cbi.instruction.bits != new_code) { Log_DebugPrintf("Block 0x%08X changed at PC 0x%08X - %08X to %08X - recompiling.", block->GetPC(), cbi.pc, @@ -242,7 +282,7 @@ recompile: return true; } -bool CodeCache::CompileBlock(CodeBlock* block) +bool CompileBlock(CodeBlock* block) { u32 pc = block->GetPC(); bool is_branch_delay_slot = false; @@ -258,12 +298,12 @@ bool CodeCache::CompileBlock(CodeBlock* block) CodeBlockInstruction cbi = {}; const PhysicalMemoryAddress phys_addr = pc & PHYSICAL_MEMORY_ADDRESS_MASK; - if (!m_bus->IsCacheableAddress(phys_addr) || - m_bus->DispatchAccess(phys_addr, cbi.instruction.bits) < 0 || - !IsInvalidInstruction(cbi.instruction)) - { + if (!Bus::IsCacheableAddress(phys_addr)) + break; + + cbi.instruction.bits = Bus::ReadCacheableAddress(phys_addr); + if (!IsInvalidInstruction(cbi.instruction)) break; - } cbi.pc = pc; cbi.is_branch_delay_slot = is_branch_delay_slot; @@ -272,7 +312,7 @@ bool CodeCache::CompileBlock(CodeBlock* block) cbi.is_load_instruction = IsMemoryLoadInstruction(cbi.instruction); cbi.is_store_instruction = IsMemoryStoreInstruction(cbi.instruction); cbi.has_load_delay = InstructionHasLoadDelay(cbi.instruction); - cbi.can_trap = CanInstructionTrap(cbi.instruction, m_core->InUserMode()); + cbi.can_trap = CanInstructionTrap(cbi.instruction, InUserMode()); // instruction is decoded now block->instructions.push_back(cbi); @@ -316,19 +356,19 @@ bool CodeCache::CompileBlock(CodeBlock* block) } #ifdef WITH_RECOMPILER - if (m_use_recompiler) + if (s_use_recompiler) { // Ensure we're not going to run out of space while compiling this block. - if (m_code_buffer->GetFreeCodeSpace() < + if (s_code_buffer.GetFreeCodeSpace() < (block->instructions.size() * Recompiler::MAX_NEAR_HOST_BYTES_PER_INSTRUCTION) || - m_code_buffer->GetFreeFarCodeSpace() < + s_code_buffer.GetFreeFarCodeSpace() < (block->instructions.size() * Recompiler::MAX_FAR_HOST_BYTES_PER_INSTRUCTION)) { Log_WarningPrintf("Out of code space, flushing all blocks."); Flush(); } - Recompiler::CodeGenerator codegen(m_core, m_code_buffer.get(), *m_asm_functions.get()); + Recompiler::CodeGenerator codegen(&s_code_buffer); if (!codegen.CompileBlock(block, &block->host_code, &block->host_code_size)) { Log_ErrorPrintf("Failed to compile host code for block at 0x%08X", block->key.GetPC()); @@ -340,7 +380,7 @@ bool CodeCache::CompileBlock(CodeBlock* block) return true; } -void CodeCache::InvalidateBlocksWithPageIndex(u32 page_index) +void InvalidateBlocksWithPageIndex(u32 page_index) { DebugAssert(page_index < CPU_CODE_CACHE_PAGE_COUNT); auto& blocks = m_ram_block_map[page_index]; @@ -353,24 +393,26 @@ void CodeCache::InvalidateBlocksWithPageIndex(u32 page_index) // Block will be re-added next execution. blocks.clear(); - m_bus->ClearRAMCodePage(page_index); + Bus::ClearRAMCodePage(page_index); } -void CodeCache::FlushBlock(CodeBlock* block) +void FlushBlock(CodeBlock* block) { - BlockMap::iterator iter = m_blocks.find(block->key.GetPC()); - Assert(iter != m_blocks.end() && iter->second == block); + BlockMap::iterator iter = s_blocks.find(block->key.GetPC()); + Assert(iter != s_blocks.end() && iter->second == block); Log_DevPrintf("Flushing block at address 0x%08X", block->GetPC()); // if it's been invalidated it won't be in the page map if (block->invalidated) RemoveBlockFromPageMap(block); - m_blocks.erase(iter); + UnlinkBlock(block); + + s_blocks.erase(iter); delete block; } -void CodeCache::AddBlockToPageMap(CodeBlock* block) +void AddBlockToPageMap(CodeBlock* block) { if (!block->IsInRAM()) return; @@ -380,11 +422,11 @@ void CodeCache::AddBlockToPageMap(CodeBlock* block) for (u32 page = start_page; page <= end_page; page++) { m_ram_block_map[page].push_back(block); - m_bus->SetRAMCodePage(page); + Bus::SetRAMCodePage(page); } } -void CodeCache::RemoveBlockFromPageMap(CodeBlock* block) +void RemoveBlockFromPageMap(CodeBlock* block) { if (!block->IsInRAM()) return; @@ -400,14 +442,14 @@ void CodeCache::RemoveBlockFromPageMap(CodeBlock* block) } } -void CodeCache::LinkBlock(CodeBlock* from, CodeBlock* to) +void LinkBlock(CodeBlock* from, CodeBlock* to) { Log_DebugPrintf("Linking block %p(%08x) to %p(%08x)", from, from->GetPC(), to, to->GetPC()); from->link_successors.push_back(to); to->link_predecessors.push_back(from); } -void CodeCache::UnlinkBlock(CodeBlock* block) +void UnlinkBlock(CodeBlock* block) { for (CodeBlock* predecessor : block->link_predecessors) { @@ -426,82 +468,4 @@ void CodeCache::UnlinkBlock(CodeBlock* block) block->link_successors.clear(); } -void CodeCache::InterpretCachedBlock(const CodeBlock& block) -{ - // set up the state so we've already fetched the instruction - DebugAssert(m_core->m_regs.pc == block.GetPC()); - - m_core->m_regs.npc = block.GetPC() + 4; - - for (const CodeBlockInstruction& cbi : block.instructions) - { - m_core->m_pending_ticks++; - - // now executing the instruction we previously fetched - m_core->m_current_instruction.bits = cbi.instruction.bits; - m_core->m_current_instruction_pc = cbi.pc; - m_core->m_current_instruction_in_branch_delay_slot = cbi.is_branch_delay_slot; - m_core->m_current_instruction_was_branch_taken = m_core->m_branch_was_taken; - m_core->m_branch_was_taken = false; - m_core->m_exception_raised = false; - - // update pc - m_core->m_regs.pc = m_core->m_regs.npc; - m_core->m_regs.npc += 4; - - // execute the instruction we previously fetched - m_core->ExecuteInstruction(); - - // next load delay - m_core->UpdateLoadDelay(); - - if (m_core->m_exception_raised) - break; - } - - // cleanup so the interpreter can kick in if needed - m_core->m_next_instruction_is_branch_delay_slot = false; -} - -void CodeCache::InterpretUncachedBlock() -{ - Panic("Fixme with regards to re-fetching PC"); - - // At this point, pc contains the last address executed (in the previous block). The instruction has not been fetched - // yet. pc shouldn't be updated until the fetch occurs, that way the exception occurs in the delay slot. - bool in_branch_delay_slot = false; - for (;;) - { - m_core->m_pending_ticks++; - - // now executing the instruction we previously fetched - m_core->m_current_instruction.bits = m_core->m_next_instruction.bits; - m_core->m_current_instruction_pc = m_core->m_regs.pc; - m_core->m_current_instruction_in_branch_delay_slot = m_core->m_next_instruction_is_branch_delay_slot; - m_core->m_current_instruction_was_branch_taken = m_core->m_branch_was_taken; - m_core->m_next_instruction_is_branch_delay_slot = false; - m_core->m_branch_was_taken = false; - m_core->m_exception_raised = false; - - // Fetch the next instruction, except if we're in a branch delay slot. The "fetch" is done in the next block. - if (!m_core->FetchInstruction()) - break; - - // execute the instruction we previously fetched - m_core->ExecuteInstruction(); - - // next load delay - m_core->UpdateLoadDelay(); - - const bool branch = IsBranchInstruction(m_core->m_current_instruction); - if (m_core->m_exception_raised || (!branch && in_branch_delay_slot) || - IsExitBlockInstruction(m_core->m_current_instruction)) - { - break; - } - - in_branch_delay_slot = branch; - } -} - -} // namespace CPU +} // namespace CPU::CodeCache diff --git a/src/core/cpu_code_cache.h b/src/core/cpu_code_cache.h index 90abf06aa..0b2f8ab88 100644 --- a/src/core/cpu_code_cache.h +++ b/src/core/cpu_code_cache.h @@ -1,22 +1,13 @@ #pragma once #include "common/bitfield.h" +#include "common/jit_code_buffer.h" #include "cpu_types.h" #include #include #include #include -class JitCodeBuffer; - -class Bus; -class System; - namespace CPU { -class Core; - -namespace Recompiler { -class ASMFunctions; -} union CodeBlockKey { @@ -58,7 +49,7 @@ struct CodeBlockInstruction struct CodeBlock { - using HostCodePointer = void (*)(Core*); + using HostCodePointer = void (*)(); CodeBlock(const CodeBlockKey key_) : key(key_) {} @@ -86,67 +77,24 @@ struct CodeBlock } }; -class CodeCache -{ -public: - CodeCache(); - ~CodeCache(); +namespace CodeCache { - void Initialize(System* system, Core* core, Bus* bus, bool use_recompiler); - void Execute(); +void Initialize(bool use_recompiler); +void Shutdown(); +void Execute(); - /// Flushes the code cache, forcing all blocks to be recompiled. - void Flush(); +/// Flushes the code cache, forcing all blocks to be recompiled. +void Flush(); - /// Changes whether the recompiler is enabled. - void SetUseRecompiler(bool enable); +/// Changes whether the recompiler is enabled. +void SetUseRecompiler(bool enable); - /// Invalidates all blocks which are in the range of the specified code page. - void InvalidateBlocksWithPageIndex(u32 page_index); +/// Invalidates all blocks which are in the range of the specified code page. +void InvalidateBlocksWithPageIndex(u32 page_index); -private: - using BlockMap = std::unordered_map; +void InterpretCachedBlock(const CodeBlock& block); +void InterpretUncachedBlock(); - void LogCurrentState(); +}; // namespace CodeCache - /// Returns the block key for the current execution state. - CodeBlockKey GetNextBlockKey() const; - - /// Looks up the block in the cache if it's already been compiled. - CodeBlock* LookupBlock(CodeBlockKey key); - - /// Can the current block execute? This will re-validate the block if necessary. - /// The block can also be flushed if recompilation failed, so ignore the pointer if false is returned. - bool RevalidateBlock(CodeBlock* block); - - bool CompileBlock(CodeBlock* block); - void FlushBlock(CodeBlock* block); - void AddBlockToPageMap(CodeBlock* block); - void RemoveBlockFromPageMap(CodeBlock* block); - - /// Link block from to to. - void LinkBlock(CodeBlock* from, CodeBlock* to); - - /// Unlink all blocks which point to this block, and any that this block links to. - void UnlinkBlock(CodeBlock* block); - - void InterpretCachedBlock(const CodeBlock& block); - void InterpretUncachedBlock(); - - System* m_system = nullptr; - Core* m_core = nullptr; - Bus* m_bus = nullptr; - -#ifdef WITH_RECOMPILER - std::unique_ptr m_code_buffer; - std::unique_ptr m_asm_functions; -#endif - - BlockMap m_blocks; - - bool m_use_recompiler = false; - - std::array, CPU_CODE_CACHE_PAGE_COUNT> m_ram_block_map; -}; - -} // namespace CPU \ No newline at end of file +} // namespace CPU diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 6faf24ee8..ee6818f66 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -3,11 +3,43 @@ #include "common/log.h" #include "common/state_wrapper.h" #include "cpu_disasm.h" +#include "cpu_recompiler_thunks.h" +#include "gte.h" +#include "timing_event.h" #include Log_SetChannel(CPU::Core); namespace CPU { +/// Sets the PC and flushes the pipeline. +static void SetPC(u32 new_pc); + +// Updates load delays - call after each instruction +static void UpdateLoadDelay(); + +// Fetches the instruction at m_regs.npc +static void ExecuteInstruction(); +static void ExecuteCop0Instruction(); +static void ExecuteCop2Instruction(); +static void Branch(u32 target); + +// exceptions +void RaiseException(Exception excode); +void RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE); + +// clears pipeline of load/branch delays +static void FlushPipeline(); + +// defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown. +bool FetchInstruction(); +bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value); +bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); +bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value); +bool WriteMemoryByte(VirtualMemoryAddress addr, u8 value); +bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); +bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value); + +State g_state; bool TRACE_EXECUTION = false; bool LOG_EXECUTION = false; @@ -34,238 +66,103 @@ void WriteToExecutionLog(const char* format, ...) va_end(ap); } -Core::Core() = default; - -Core::~Core() = default; - -void Core::Initialize(Bus* bus) +void Initialize() { - m_bus = bus; - // From nocash spec. - m_cop0_regs.PRID = UINT32_C(0x00000002); + g_state.cop0_regs.PRID = UINT32_C(0x00000002); - m_cop2.Initialize(); + GTE::Initialize(); } -void Core::Reset() +void Shutdown() { - m_pending_ticks = 0; - m_downcount = MAX_SLICE_SIZE; + // GTE::Shutdown(); +} - m_regs = {}; +void Reset() +{ + g_state.pending_ticks = 0; + g_state.downcount = MAX_SLICE_SIZE; - m_cop0_regs.BPC = 0; - m_cop0_regs.BDA = 0; - m_cop0_regs.TAR = 0; - m_cop0_regs.BadVaddr = 0; - m_cop0_regs.BDAM = 0; - m_cop0_regs.BPCM = 0; - m_cop0_regs.EPC = 0; - m_cop0_regs.sr.bits = 0; - m_cop0_regs.cause.bits = 0; + g_state.regs = {}; - m_cop2.Reset(); + g_state.cop0_regs.BPC = 0; + g_state.cop0_regs.BDA = 0; + g_state.cop0_regs.TAR = 0; + g_state.cop0_regs.BadVaddr = 0; + g_state.cop0_regs.BDAM = 0; + g_state.cop0_regs.BPCM = 0; + g_state.cop0_regs.EPC = 0; + g_state.cop0_regs.sr.bits = 0; + g_state.cop0_regs.cause.bits = 0; + + GTE::Reset(); SetPC(RESET_VECTOR); } -bool Core::DoState(StateWrapper& sw) +bool DoState(StateWrapper& sw) { - sw.Do(&m_pending_ticks); - sw.Do(&m_downcount); - sw.DoArray(m_regs.r, countof(m_regs.r)); - sw.Do(&m_cop0_regs.BPC); - sw.Do(&m_cop0_regs.BDA); - sw.Do(&m_cop0_regs.TAR); - sw.Do(&m_cop0_regs.BadVaddr); - sw.Do(&m_cop0_regs.BDAM); - sw.Do(&m_cop0_regs.BPCM); - sw.Do(&m_cop0_regs.EPC); - sw.Do(&m_cop0_regs.PRID); - sw.Do(&m_cop0_regs.sr.bits); - sw.Do(&m_cop0_regs.cause.bits); - sw.Do(&m_cop0_regs.dcic.bits); - sw.Do(&m_next_instruction.bits); - sw.Do(&m_current_instruction.bits); - sw.Do(&m_current_instruction_pc); - sw.Do(&m_current_instruction_in_branch_delay_slot); - sw.Do(&m_current_instruction_was_branch_taken); - sw.Do(&m_next_instruction_is_branch_delay_slot); - sw.Do(&m_branch_was_taken); - sw.Do(&m_exception_raised); - sw.Do(&m_interrupt_delay); - sw.Do(&m_load_delay_reg); - sw.Do(&m_load_delay_value); - sw.Do(&m_next_load_delay_reg); - sw.Do(&m_next_load_delay_value); - sw.Do(&m_cache_control); - sw.DoBytes(m_dcache.data(), m_dcache.size()); + sw.Do(&g_state.pending_ticks); + sw.Do(&g_state.downcount); + sw.DoArray(g_state.regs.r, countof(g_state.regs.r)); + sw.Do(&g_state.cop0_regs.BPC); + sw.Do(&g_state.cop0_regs.BDA); + sw.Do(&g_state.cop0_regs.TAR); + sw.Do(&g_state.cop0_regs.BadVaddr); + sw.Do(&g_state.cop0_regs.BDAM); + sw.Do(&g_state.cop0_regs.BPCM); + sw.Do(&g_state.cop0_regs.EPC); + sw.Do(&g_state.cop0_regs.PRID); + sw.Do(&g_state.cop0_regs.sr.bits); + sw.Do(&g_state.cop0_regs.cause.bits); + sw.Do(&g_state.cop0_regs.dcic.bits); + sw.Do(&g_state.next_instruction.bits); + sw.Do(&g_state.current_instruction.bits); + sw.Do(&g_state.current_instruction_pc); + sw.Do(&g_state.current_instruction_in_branch_delay_slot); + sw.Do(&g_state.current_instruction_was_branch_taken); + sw.Do(&g_state.next_instruction_is_branch_delay_slot); + sw.Do(&g_state.branch_was_taken); + sw.Do(&g_state.exception_raised); + sw.Do(&g_state.interrupt_delay); + sw.Do(&g_state.load_delay_reg); + sw.Do(&g_state.load_delay_value); + sw.Do(&g_state.next_load_delay_reg); + sw.Do(&g_state.next_load_delay_value); + sw.Do(&g_state.cache_control); + sw.DoBytes(g_state.dcache.data(), g_state.dcache.size()); - if (!m_cop2.DoState(sw)) + if (!GTE::DoState(sw)) return false; return !sw.HasError(); } -void Core::SetPC(u32 new_pc) +void SetPC(u32 new_pc) { DebugAssert(Common::IsAlignedPow2(new_pc, 4)); - m_regs.npc = new_pc; + g_state.regs.npc = new_pc; FlushPipeline(); } -bool Core::ReadMemoryByte(VirtualMemoryAddress addr, u8* value) -{ - u32 temp = 0; - const TickCount cycles = DoMemoryAccess(addr, temp); - *value = Truncate8(temp); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - m_pending_ticks += cycles; - return true; -} - -bool Core::ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value) -{ - if (!DoAlignmentCheck(addr)) - return false; - - u32 temp = 0; - const TickCount cycles = DoMemoryAccess(addr, temp); - *value = Truncate16(temp); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - m_pending_ticks += cycles; - return true; -} - -bool Core::ReadMemoryWord(VirtualMemoryAddress addr, u32* value) -{ - if (!DoAlignmentCheck(addr)) - return false; - - const TickCount cycles = DoMemoryAccess(addr, *value); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - m_pending_ticks += cycles; - return true; -} - -bool Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value) -{ - u32 temp = ZeroExtend32(value); - const TickCount cycles = DoMemoryAccess(addr, temp); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Core::WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) -{ - if (!DoAlignmentCheck(addr)) - return false; - - u32 temp = ZeroExtend32(value); - const TickCount cycles = DoMemoryAccess(addr, temp); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Core::WriteMemoryWord(VirtualMemoryAddress addr, u32 value) -{ - if (!DoAlignmentCheck(addr)) - return false; - - const TickCount cycles = DoMemoryAccess(addr, value); - if (cycles < 0) - { - RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Core::SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value) -{ - u32 temp = 0; - const TickCount cycles = DoMemoryAccess(addr, temp); - *value = Truncate8(temp); - return (cycles >= 0); -} - -bool Core::SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value) -{ - u32 temp = 0; - const TickCount cycles = DoMemoryAccess(addr, temp); - *value = Truncate16(temp); - return (cycles >= 0); -} - -bool Core::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value) -{ - return DoMemoryAccess(addr, *value) >= 0; -} - -bool Core::SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value) -{ - u32 temp = ZeroExtend32(value); - return DoMemoryAccess(addr, temp) >= 0; -} - -bool Core::SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) -{ - u32 temp = ZeroExtend32(value); - return DoMemoryAccess(addr, temp) >= 0; -} - -bool Core::SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value) -{ - return DoMemoryAccess(addr, value) >= 0; -} - -void Core::Branch(u32 target) +void Branch(u32 target) { if (!Common::IsAlignedPow2(target, 4)) { // The BadVaddr and EPC must be set to the fetching address, not the instruction about to execute. - m_cop0_regs.BadVaddr = target; + g_state.cop0_regs.BadVaddr = target; RaiseException(Exception::AdEL, target, false, false, 0); return; } - m_regs.npc = target; - m_branch_was_taken = true; + g_state.regs.npc = target; + g_state.branch_was_taken = true; } -u32 Core::GetExceptionVector(Exception excode) const +ALWAYS_INLINE static u32 GetExceptionVector(Exception excode) { - const u32 base = m_cop0_regs.sr.BEV ? UINT32_C(0xbfc00100) : UINT32_C(0x80000000); + const u32 base = g_state.cop0_regs.sr.BEV ? UINT32_C(0xbfc00100) : UINT32_C(0x80000000); #if 0 // apparently this isn't correct... @@ -282,175 +179,187 @@ u32 Core::GetExceptionVector(Exception excode) const #endif } -void Core::RaiseException(Exception excode) +void RaiseException(Exception excode) { - RaiseException(excode, m_current_instruction_pc, m_current_instruction_in_branch_delay_slot, - m_current_instruction_was_branch_taken, m_current_instruction.cop.cop_n); + RaiseException(excode, g_state.current_instruction_pc, g_state.current_instruction_in_branch_delay_slot, + g_state.current_instruction_was_branch_taken, g_state.current_instruction.cop.cop_n); } -void Core::RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE) +void RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE) { #ifdef _DEBUG if (excode != Exception::INT && excode != Exception::Syscall && excode != Exception::BP) { Log_DebugPrintf("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)", static_cast(excode), - m_current_instruction_pc, EPC, BD ? "true" : "false", ZeroExtend32(CE)); - DisassembleAndPrint(m_current_instruction_pc, 4, 0); + g_state.current_instruction_pc, EPC, BD ? "true" : "false", ZeroExtend32(CE)); + DisassembleAndPrint(g_state.current_instruction_pc, 4, 0); if (LOG_EXECUTION) { CPU::WriteToExecutionLog("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)\n", static_cast(excode), - m_current_instruction_pc, EPC, BD ? "true" : "false", ZeroExtend32(CE)); + g_state.current_instruction_pc, EPC, BD ? "true" : "false", ZeroExtend32(CE)); } } #endif - m_cop0_regs.EPC = EPC; - m_cop0_regs.cause.Excode = excode; - m_cop0_regs.cause.BD = BD; - m_cop0_regs.cause.BT = BT; - m_cop0_regs.cause.CE = CE; + g_state.cop0_regs.EPC = EPC; + g_state.cop0_regs.cause.Excode = excode; + g_state.cop0_regs.cause.BD = BD; + g_state.cop0_regs.cause.BT = BT; + g_state.cop0_regs.cause.CE = CE; if (BD) { // TAR is set to the address which was being fetched in this instruction, or the next instruction to execute if the // exception hadn't occurred in the delay slot. - m_cop0_regs.EPC -= UINT32_C(4); - m_cop0_regs.TAR = m_regs.pc; + g_state.cop0_regs.EPC -= UINT32_C(4); + g_state.cop0_regs.TAR = g_state.regs.pc; } // current -> previous, switch to kernel mode and disable interrupts - m_cop0_regs.sr.mode_bits <<= 2; + g_state.cop0_regs.sr.mode_bits <<= 2; // flush the pipeline - we don't want to execute the previously fetched instruction - m_regs.npc = GetExceptionVector(excode); - m_exception_raised = true; + g_state.regs.npc = GetExceptionVector(excode); + g_state.exception_raised = true; FlushPipeline(); } -void Core::SetExternalInterrupt(u8 bit) +void SetExternalInterrupt(u8 bit) { - m_cop0_regs.cause.Ip |= static_cast(1u << bit); - m_interrupt_delay = 1; + g_state.cop0_regs.cause.Ip |= static_cast(1u << bit); + g_state.interrupt_delay = 1; } -void Core::ClearExternalInterrupt(u8 bit) +void ClearExternalInterrupt(u8 bit) { - m_cop0_regs.cause.Ip &= static_cast(~(1u << bit)); + g_state.cop0_regs.cause.Ip &= static_cast(~(1u << bit)); } -bool Core::HasPendingInterrupt() +bool HasPendingInterrupt() { - // const bool do_interrupt = m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0); - const bool do_interrupt = - m_cop0_regs.sr.IEc && (((m_cop0_regs.cause.bits & m_cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0); + // const bool do_interrupt = g_state.m_cop0_regs.sr.IEc && ((g_state.m_cop0_regs.cause.Ip & g_state.m_cop0_regs.sr.Im) + // != 0); + const bool do_interrupt = g_state.cop0_regs.sr.IEc && + (((g_state.cop0_regs.cause.bits & g_state.cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0); - const bool interrupt_delay = m_interrupt_delay; - m_interrupt_delay = false; + const bool interrupt_delay = g_state.interrupt_delay; + g_state.interrupt_delay = false; return do_interrupt && !interrupt_delay; } -void Core::DispatchInterrupt() +void DispatchInterrupt() { // If the instruction we're about to execute is a GTE instruction, delay dispatching the interrupt until the next // instruction. For some reason, if we don't do this, we end up with incorrectly sorted polygons and flickering.. - if (m_next_instruction.IsCop2Instruction()) + if (g_state.next_instruction.IsCop2Instruction()) return; // Interrupt raising occurs before the start of the instruction. - RaiseException(Exception::INT, m_regs.pc, m_next_instruction_is_branch_delay_slot, m_branch_was_taken, - m_next_instruction.cop.cop_n); + RaiseException(Exception::INT, g_state.regs.pc, g_state.next_instruction_is_branch_delay_slot, + g_state.branch_was_taken, g_state.next_instruction.cop.cop_n); } -void Core::FlushPipeline() +void UpdateLoadDelay() +{ + // the old value is needed in case the delay slot instruction overwrites the same register + if (g_state.load_delay_reg != Reg::count) + g_state.regs.r[static_cast(g_state.load_delay_reg)] = g_state.load_delay_value; + + g_state.load_delay_reg = g_state.next_load_delay_reg; + g_state.load_delay_value = g_state.next_load_delay_value; + g_state.next_load_delay_reg = Reg::count; +} + +void FlushPipeline() { // loads are flushed - m_next_load_delay_reg = Reg::count; - if (m_load_delay_reg != Reg::count) + g_state.next_load_delay_reg = Reg::count; + if (g_state.load_delay_reg != Reg::count) { - m_regs.r[static_cast(m_load_delay_reg)] = m_load_delay_value; - m_load_delay_reg = Reg::count; + g_state.regs.r[static_cast(g_state.load_delay_reg)] = g_state.load_delay_value; + g_state.load_delay_reg = Reg::count; } // not in a branch delay slot - m_branch_was_taken = false; - m_next_instruction_is_branch_delay_slot = false; - m_current_instruction_pc = m_regs.pc; + g_state.branch_was_taken = false; + g_state.next_instruction_is_branch_delay_slot = false; + g_state.current_instruction_pc = g_state.regs.pc; // prefetch the next instruction FetchInstruction(); // and set it as the next one to execute - m_current_instruction.bits = m_next_instruction.bits; - m_current_instruction_in_branch_delay_slot = false; - m_current_instruction_was_branch_taken = false; + g_state.current_instruction.bits = g_state.next_instruction.bits; + g_state.current_instruction_in_branch_delay_slot = false; + g_state.current_instruction_was_branch_taken = false; } -u32 Core::ReadReg(Reg rs) +ALWAYS_INLINE u32 ReadReg(Reg rs) { - return m_regs.r[static_cast(rs)]; + return g_state.regs.r[static_cast(rs)]; } -void Core::WriteReg(Reg rd, u32 value) +ALWAYS_INLINE void WriteReg(Reg rd, u32 value) { - m_regs.r[static_cast(rd)] = value; - m_load_delay_reg = (rd == m_load_delay_reg) ? Reg::count : m_load_delay_reg; + g_state.regs.r[static_cast(rd)] = value; + g_state.load_delay_reg = (rd == g_state.load_delay_reg) ? Reg::count : g_state.load_delay_reg; // prevent writes to $zero from going through - better than branching/cmov - m_regs.zero = 0; + g_state.regs.zero = 0; } -void Core::WriteRegDelayed(Reg rd, u32 value) +static void WriteRegDelayed(Reg rd, u32 value) { - Assert(m_next_load_delay_reg == Reg::count); + Assert(g_state.next_load_delay_reg == Reg::count); if (rd == Reg::zero) return; // double load delays ignore the first value - if (m_load_delay_reg == rd) - m_load_delay_reg = Reg::count; + if (g_state.load_delay_reg == rd) + g_state.load_delay_reg = Reg::count; // save the old value, if something else overwrites this reg we want to preserve it - m_next_load_delay_reg = rd; - m_next_load_delay_value = value; + g_state.next_load_delay_reg = rd; + g_state.next_load_delay_value = value; } -std::optional Core::ReadCop0Reg(Cop0Reg reg) +static std::optional ReadCop0Reg(Cop0Reg reg) { switch (reg) { case Cop0Reg::BPC: - return m_cop0_regs.BPC; + return g_state.cop0_regs.BPC; case Cop0Reg::BPCM: - return m_cop0_regs.BPCM; + return g_state.cop0_regs.BPCM; case Cop0Reg::BDA: - return m_cop0_regs.BDA; + return g_state.cop0_regs.BDA; case Cop0Reg::BDAM: - return m_cop0_regs.BDAM; + return g_state.cop0_regs.BDAM; case Cop0Reg::DCIC: - return m_cop0_regs.dcic.bits; + return g_state.cop0_regs.dcic.bits; case Cop0Reg::JUMPDEST: - return m_cop0_regs.TAR; + return g_state.cop0_regs.TAR; case Cop0Reg::BadVaddr: - return m_cop0_regs.BadVaddr; + return g_state.cop0_regs.BadVaddr; case Cop0Reg::SR: - return m_cop0_regs.sr.bits; + return g_state.cop0_regs.sr.bits; case Cop0Reg::CAUSE: - return m_cop0_regs.cause.bits; + return g_state.cop0_regs.cause.bits; case Cop0Reg::EPC: - return m_cop0_regs.EPC; + return g_state.cop0_regs.EPC; case Cop0Reg::PRID: - return m_cop0_regs.PRID; + return g_state.cop0_regs.PRID; default: Log_DevPrintf("Unknown COP0 reg %u", ZeroExtend32(static_cast(reg))); @@ -458,34 +367,34 @@ std::optional Core::ReadCop0Reg(Cop0Reg reg) } } -void Core::WriteCop0Reg(Cop0Reg reg, u32 value) +static void WriteCop0Reg(Cop0Reg reg, u32 value) { switch (reg) { case Cop0Reg::BPC: { - m_cop0_regs.BPC = value; + g_state.cop0_regs.BPC = value; Log_WarningPrintf("COP0 BPC <- %08X", value); } break; case Cop0Reg::BPCM: { - m_cop0_regs.BPCM = value; + g_state.cop0_regs.BPCM = value; Log_WarningPrintf("COP0 BPCM <- %08X", value); } break; case Cop0Reg::BDA: { - m_cop0_regs.BDA = value; + g_state.cop0_regs.BDA = value; Log_WarningPrintf("COP0 BDA <- %08X", value); } break; case Cop0Reg::BDAM: { - m_cop0_regs.BDAM = value; + g_state.cop0_regs.BDAM = value; Log_WarningPrintf("COP0 BDAM <- %08X", value); } break; @@ -498,25 +407,25 @@ void Core::WriteCop0Reg(Cop0Reg reg, u32 value) case Cop0Reg::DCIC: { - m_cop0_regs.dcic.bits = - (m_cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK); - Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, m_cop0_regs.dcic.bits); + g_state.cop0_regs.dcic.bits = + (g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK); + Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, g_state.cop0_regs.dcic.bits); } break; case Cop0Reg::SR: { - m_cop0_regs.sr.bits = - (m_cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK); - Log_DebugPrintf("COP0 SR <- %08X (now %08X)", value, m_cop0_regs.sr.bits); + g_state.cop0_regs.sr.bits = + (g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK); + Log_DebugPrintf("COP0 SR <- %08X (now %08X)", value, g_state.cop0_regs.sr.bits); } break; case Cop0Reg::CAUSE: { - m_cop0_regs.cause.bits = - (m_cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | (value & Cop0Registers::CAUSE::WRITE_MASK); - Log_DebugPrintf("COP0 CAUSE <- %08X (now %08X)", value, m_cop0_regs.cause.bits); + g_state.cop0_regs.cause.bits = + (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | (value & Cop0Registers::CAUSE::WRITE_MASK); + Log_DebugPrintf("COP0 CAUSE <- %08X (now %08X)", value, g_state.cop0_regs.cause.bits); } break; @@ -526,24 +435,18 @@ void Core::WriteCop0Reg(Cop0Reg reg, u32 value) } } -void Core::WriteCacheControl(u32 value) -{ - Log_WarningPrintf("Cache control <- 0x%08X", value); - m_cache_control = value; -} - -static void PrintInstruction(u32 bits, u32 pc, Core* state) +static void PrintInstruction(u32 bits, u32 pc, Registers* regs) { TinyString instr; - DisassembleInstruction(&instr, pc, bits, state); + DisassembleInstruction(&instr, pc, bits, regs); std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); } -static void LogInstruction(u32 bits, u32 pc, Core* state) +static void LogInstruction(u32 bits, u32 pc, Registers* regs) { TinyString instr; - DisassembleInstruction(&instr, pc, bits, state); + DisassembleInstruction(&instr, pc, bits, regs); WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); } @@ -558,14 +461,14 @@ static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value) return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0; } -void Core::DisassembleAndPrint(u32 addr) +void DisassembleAndPrint(u32 addr) { u32 bits = 0; - DoMemoryAccess(addr, bits); - PrintInstruction(bits, addr, this); + SafeReadMemoryWord(addr, &bits); + PrintInstruction(bits, addr, &g_state.regs); } -void Core::DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instructions_after /* = 0 */) +void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instructions_after /* = 0 */) { u32 disasm_addr = addr - (instructions_before * sizeof(u32)); for (u32 i = 0; i < instructions_before; i++) @@ -584,65 +487,58 @@ void Core::DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 } } -void Core::Execute() +void Execute() { - while (m_pending_ticks <= m_downcount) + g_state.frame_done = false; + while (!g_state.frame_done) { - if (HasPendingInterrupt()) - DispatchInterrupt(); + TimingEvents::UpdateCPUDowncount(); - m_pending_ticks++; + while (g_state.pending_ticks <= g_state.downcount) + { + if (HasPendingInterrupt()) + DispatchInterrupt(); - // now executing the instruction we previously fetched - m_current_instruction.bits = m_next_instruction.bits; - m_current_instruction_pc = m_regs.pc; - m_current_instruction_in_branch_delay_slot = m_next_instruction_is_branch_delay_slot; - m_current_instruction_was_branch_taken = m_branch_was_taken; - m_next_instruction_is_branch_delay_slot = false; - m_branch_was_taken = false; - m_exception_raised = false; + g_state.pending_ticks++; - // fetch the next instruction - if (!FetchInstruction()) - continue; + // now executing the instruction we previously fetched + g_state.current_instruction.bits = g_state.next_instruction.bits; + g_state.current_instruction_pc = g_state.regs.pc; + g_state.current_instruction_in_branch_delay_slot = g_state.next_instruction_is_branch_delay_slot; + g_state.current_instruction_was_branch_taken = g_state.branch_was_taken; + g_state.next_instruction_is_branch_delay_slot = false; + g_state.branch_was_taken = false; + g_state.exception_raised = false; + + // fetch the next instruction + if (!FetchInstruction()) + continue; #if 0 // GTE flag test debugging - if (m_current_instruction_pc == 0x8002cdf4) - { - if (m_regs.v1 != m_regs.v0) - printf("Got %08X Expected? %08X\n", m_regs.v1, m_regs.v0); - } + if (g_state.m_current_instruction_pc == 0x8002cdf4) + { + if (g_state.m_regs.v1 != g_state.m_regs.v0) + printf("Got %08X Expected? %08X\n", g_state.m_regs.v1, g_state.m_regs.v0); + } #endif - // execute the instruction we previously fetched - ExecuteInstruction(); + // execute the instruction we previously fetched + ExecuteInstruction(); - // next load delay - UpdateLoadDelay(); + // next load delay + UpdateLoadDelay(); + } + + TimingEvents::RunEvents(); } } -bool Core::FetchInstruction() +void ExecuteInstruction() { - DebugAssert(Common::IsAlignedPow2(m_regs.npc, 4)); - if (DoMemoryAccess(m_regs.npc, m_next_instruction.bits) < 0) - { - // Bus errors don't set BadVaddr. - RaiseException(Exception::IBE, m_regs.npc, false, false, 0); - return false; - } - - m_regs.pc = m_regs.npc; - m_regs.npc += sizeof(m_next_instruction.bits); - return true; -} - -void Core::ExecuteInstruction() -{ - const Instruction inst = m_current_instruction; + const Instruction inst = g_state.current_instruction; #if 0 - if (m_current_instruction_pc == 0x80010000) + if (g_state.m_current_instruction_pc == 0x80010000) { LOG_EXECUTION = true; __debugbreak(); @@ -650,7 +546,7 @@ void Core::ExecuteInstruction() #endif #if 0 - if (m_current_instruction_pc == 0x8002bf50) + if (g_state.m_current_instruction_pc == 0x8002bf50) { TRACE_EXECUTION = true; __debugbreak(); @@ -659,9 +555,9 @@ void Core::ExecuteInstruction() #ifdef _DEBUG if (TRACE_EXECUTION) - PrintInstruction(inst.bits, m_current_instruction_pc, this); + PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); if (LOG_EXECUTION) - LogInstruction(inst.bits, m_current_instruction_pc, this); + LogInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); #endif switch (inst.op) @@ -803,27 +699,27 @@ void Core::ExecuteInstruction() case InstructionFunct::mfhi: { - WriteReg(inst.r.rd, m_regs.hi); + WriteReg(inst.r.rd, g_state.regs.hi); } break; case InstructionFunct::mthi: { const u32 value = ReadReg(inst.r.rs); - m_regs.hi = value; + g_state.regs.hi = value; } break; case InstructionFunct::mflo: { - WriteReg(inst.r.rd, m_regs.lo); + WriteReg(inst.r.rd, g_state.regs.lo); } break; case InstructionFunct::mtlo: { const u32 value = ReadReg(inst.r.rs); - m_regs.lo = value; + g_state.regs.lo = value; } break; @@ -833,8 +729,8 @@ void Core::ExecuteInstruction() const u32 rhs = ReadReg(inst.r.rt); const u64 result = static_cast(static_cast(SignExtend64(lhs)) * static_cast(SignExtend64(rhs))); - m_regs.hi = Truncate32(result >> 32); - m_regs.lo = Truncate32(result); + g_state.regs.hi = Truncate32(result >> 32); + g_state.regs.lo = Truncate32(result); } break; @@ -843,8 +739,8 @@ void Core::ExecuteInstruction() const u32 lhs = ReadReg(inst.r.rs); const u32 rhs = ReadReg(inst.r.rt); const u64 result = ZeroExtend64(lhs) * ZeroExtend64(rhs); - m_regs.hi = Truncate32(result >> 32); - m_regs.lo = Truncate32(result); + g_state.regs.hi = Truncate32(result >> 32); + g_state.regs.lo = Truncate32(result); } break; @@ -856,19 +752,19 @@ void Core::ExecuteInstruction() if (denom == 0) { // divide by zero - m_regs.lo = (num >= 0) ? UINT32_C(0xFFFFFFFF) : UINT32_C(1); - m_regs.hi = static_cast(num); + g_state.regs.lo = (num >= 0) ? UINT32_C(0xFFFFFFFF) : UINT32_C(1); + g_state.regs.hi = static_cast(num); } else if (static_cast(num) == UINT32_C(0x80000000) && denom == -1) { // unrepresentable - m_regs.lo = UINT32_C(0x80000000); - m_regs.hi = 0; + g_state.regs.lo = UINT32_C(0x80000000); + g_state.regs.hi = 0; } else { - m_regs.lo = static_cast(num / denom); - m_regs.hi = static_cast(num % denom); + g_state.regs.lo = static_cast(num / denom); + g_state.regs.hi = static_cast(num % denom); } } break; @@ -881,20 +777,20 @@ void Core::ExecuteInstruction() if (denom == 0) { // divide by zero - m_regs.lo = UINT32_C(0xFFFFFFFF); - m_regs.hi = static_cast(num); + g_state.regs.lo = UINT32_C(0xFFFFFFFF); + g_state.regs.hi = static_cast(num); } else { - m_regs.lo = num / denom; - m_regs.hi = num % denom; + g_state.regs.lo = num / denom; + g_state.regs.hi = num % denom; } } break; case InstructionFunct::jr: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const u32 target = ReadReg(inst.r.rs); Branch(target); } @@ -902,9 +798,9 @@ void Core::ExecuteInstruction() case InstructionFunct::jalr: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const u32 target = ReadReg(inst.r.rs); - WriteReg(inst.r.rd, m_regs.npc); + WriteReg(inst.r.rd, g_state.regs.npc); Branch(target); } break; @@ -1054,7 +950,7 @@ void Core::ExecuteInstruction() return; // Bypasses load delay. No need to check the old value since this is the delay slot or it's not relevant. - const u32 existing_value = (inst.i.rt == m_load_delay_reg) ? m_load_delay_value : ReadReg(inst.i.rt); + const u32 existing_value = (inst.i.rt == g_state.load_delay_reg) ? g_state.load_delay_value : ReadReg(inst.i.rt); const u8 shift = (Truncate8(addr) & u8(3)) * u8(8); u32 new_value; if (inst.op == InstructionOp::lwl) @@ -1125,59 +1021,59 @@ void Core::ExecuteInstruction() case InstructionOp::j: { - m_next_instruction_is_branch_delay_slot = true; - Branch((m_regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2)); + g_state.next_instruction_is_branch_delay_slot = true; + Branch((g_state.regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2)); } break; case InstructionOp::jal: { - WriteReg(Reg::ra, m_regs.npc); - m_next_instruction_is_branch_delay_slot = true; - Branch((m_regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2)); + WriteReg(Reg::ra, g_state.regs.npc); + g_state.next_instruction_is_branch_delay_slot = true; + Branch((g_state.regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2)); } break; case InstructionOp::beq: { // We're still flagged as a branch delay slot even if the branch isn't taken. - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const bool branch = (ReadReg(inst.i.rs) == ReadReg(inst.i.rt)); if (branch) - Branch(m_regs.pc + (inst.i.imm_sext32() << 2)); + Branch(g_state.regs.pc + (inst.i.imm_sext32() << 2)); } break; case InstructionOp::bne: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const bool branch = (ReadReg(inst.i.rs) != ReadReg(inst.i.rt)); if (branch) - Branch(m_regs.pc + (inst.i.imm_sext32() << 2)); + Branch(g_state.regs.pc + (inst.i.imm_sext32() << 2)); } break; case InstructionOp::bgtz: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const bool branch = (static_cast(ReadReg(inst.i.rs)) > 0); if (branch) - Branch(m_regs.pc + (inst.i.imm_sext32() << 2)); + Branch(g_state.regs.pc + (inst.i.imm_sext32() << 2)); } break; case InstructionOp::blez: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const bool branch = (static_cast(ReadReg(inst.i.rs)) <= 0); if (branch) - Branch(m_regs.pc + (inst.i.imm_sext32() << 2)); + Branch(g_state.regs.pc + (inst.i.imm_sext32() << 2)); } break; case InstructionOp::b: { - m_next_instruction_is_branch_delay_slot = true; + g_state.next_instruction_is_branch_delay_slot = true; const u8 rt = static_cast(inst.i.rt.GetValue()); // bgez is the inverse of bltz, so simply do ltz and xor the result @@ -1187,16 +1083,16 @@ void Core::ExecuteInstruction() // register is still linked even if the branch isn't taken const bool link = (rt & u8(0x1E)) == u8(0x10); if (link) - WriteReg(Reg::ra, m_regs.npc); + WriteReg(Reg::ra, g_state.regs.npc); if (branch) - Branch(m_regs.pc + (inst.i.imm_sext32() << 2)); + Branch(g_state.regs.pc + (inst.i.imm_sext32() << 2)); } break; case InstructionOp::cop0: { - if (InUserMode() && !m_cop0_regs.sr.CU0) + if (InUserMode() && !g_state.cop0_regs.sr.CU0) { Log_WarningPrintf("Coprocessor 0 not present in user mode"); RaiseException(Exception::CpU); @@ -1209,7 +1105,7 @@ void Core::ExecuteInstruction() case InstructionOp::cop2: { - if (InUserMode() && !m_cop0_regs.sr.CU2) + if (InUserMode() && !g_state.cop0_regs.sr.CU2) { Log_WarningPrintf("Coprocessor 2 not present in user mode"); RaiseException(Exception::CpU); @@ -1222,7 +1118,7 @@ void Core::ExecuteInstruction() case InstructionOp::lwc2: { - if (InUserMode() && !m_cop0_regs.sr.CU2) + if (InUserMode() && !g_state.cop0_regs.sr.CU2) { Log_WarningPrintf("Coprocessor 2 not present in user mode"); RaiseException(Exception::CpU); @@ -1234,13 +1130,13 @@ void Core::ExecuteInstruction() if (!ReadMemoryWord(addr, &value)) return; - m_cop2.WriteRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue())), value); + GTE::WriteRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue())), value); } break; case InstructionOp::swc2: { - if (InUserMode() && !m_cop0_regs.sr.CU2) + if (InUserMode() && !g_state.cop0_regs.sr.CU2) { Log_WarningPrintf("Coprocessor 2 not present in user mode"); RaiseException(Exception::CpU); @@ -1248,7 +1144,7 @@ void Core::ExecuteInstruction() } const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); - const u32 value = m_cop2.ReadRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue()))); + const u32 value = GTE::ReadRegister(ZeroExtend32(static_cast(inst.i.rt.GetValue()))); WriteMemoryWord(addr, value); } break; @@ -1275,9 +1171,9 @@ void Core::ExecuteInstruction() } } -void Core::ExecuteCop0Instruction() +void ExecuteCop0Instruction() { - const Instruction inst = m_current_instruction; + const Instruction inst = g_state.current_instruction; if (inst.cop.IsCommonInstruction()) { @@ -1311,7 +1207,8 @@ void Core::ExecuteCop0Instruction() case Cop0Instruction::rfe: { // restore mode - m_cop0_regs.sr.mode_bits = (m_cop0_regs.sr.mode_bits & UINT32_C(0b110000)) | (m_cop0_regs.sr.mode_bits >> 2); + g_state.cop0_regs.sr.mode_bits = + (g_state.cop0_regs.sr.mode_bits & UINT32_C(0b110000)) | (g_state.cop0_regs.sr.mode_bits >> 2); } break; @@ -1322,9 +1219,9 @@ void Core::ExecuteCop0Instruction() } } -void Core::ExecuteCop2Instruction() +void ExecuteCop2Instruction() { - const Instruction inst = m_current_instruction; + const Instruction inst = g_state.current_instruction; if (inst.cop.IsCommonInstruction()) { @@ -1332,19 +1229,19 @@ void Core::ExecuteCop2Instruction() switch (inst.cop.CommonOp()) { case CopCommonInstruction::cfcn: - WriteRegDelayed(inst.r.rt, m_cop2.ReadRegister(static_cast(inst.r.rd.GetValue()) + 32)); + WriteRegDelayed(inst.r.rt, GTE::ReadRegister(static_cast(inst.r.rd.GetValue()) + 32)); break; case CopCommonInstruction::ctcn: - m_cop2.WriteRegister(static_cast(inst.r.rd.GetValue()) + 32, ReadReg(inst.r.rt)); + GTE::WriteRegister(static_cast(inst.r.rd.GetValue()) + 32, ReadReg(inst.r.rt)); break; case CopCommonInstruction::mfcn: - WriteRegDelayed(inst.r.rt, m_cop2.ReadRegister(static_cast(inst.r.rd.GetValue()))); + WriteRegDelayed(inst.r.rt, GTE::ReadRegister(static_cast(inst.r.rd.GetValue()))); break; case CopCommonInstruction::mtcn: - m_cop2.WriteRegister(static_cast(inst.r.rd.GetValue()), ReadReg(inst.r.rt)); + GTE::WriteRegister(static_cast(inst.r.rd.GetValue()), ReadReg(inst.r.rt)); break; case CopCommonInstruction::bcnc: @@ -1355,8 +1252,115 @@ void Core::ExecuteCop2Instruction() } else { - m_cop2.ExecuteInstruction(GTE::Instruction{inst.bits}); + GTE::ExecuteInstruction(inst.bits); } } -} // namespace CPU +namespace CodeCache { + +void InterpretCachedBlock(const CodeBlock& block) +{ + // set up the state so we've already fetched the instruction + DebugAssert(g_state.regs.pc == block.GetPC()); + + g_state.regs.npc = block.GetPC() + 4; + + for (const CodeBlockInstruction& cbi : block.instructions) + { + g_state.pending_ticks++; + + // now executing the instruction we previously fetched + g_state.current_instruction.bits = cbi.instruction.bits; + g_state.current_instruction_pc = cbi.pc; + g_state.current_instruction_in_branch_delay_slot = cbi.is_branch_delay_slot; + g_state.current_instruction_was_branch_taken = g_state.branch_was_taken; + g_state.branch_was_taken = false; + g_state.exception_raised = false; + + // update pc + g_state.regs.pc = g_state.regs.npc; + g_state.regs.npc += 4; + + // execute the instruction we previously fetched + ExecuteInstruction(); + + // next load delay + UpdateLoadDelay(); + + if (g_state.exception_raised) + break; + } + + // cleanup so the interpreter can kick in if needed + g_state.next_instruction_is_branch_delay_slot = false; +} + +void InterpretUncachedBlock() +{ + Panic("Fixme with regards to re-fetching PC"); + + // At this point, pc contains the last address executed (in the previous block). The instruction has not been fetched + // yet. pc shouldn't be updated until the fetch occurs, that way the exception occurs in the delay slot. + bool in_branch_delay_slot = false; + for (;;) + { + g_state.pending_ticks++; + + // now executing the instruction we previously fetched + g_state.current_instruction.bits = g_state.next_instruction.bits; + g_state.current_instruction_pc = g_state.regs.pc; + g_state.current_instruction_in_branch_delay_slot = g_state.next_instruction_is_branch_delay_slot; + g_state.current_instruction_was_branch_taken = g_state.branch_was_taken; + g_state.next_instruction_is_branch_delay_slot = false; + g_state.branch_was_taken = false; + g_state.exception_raised = false; + + // Fetch the next instruction, except if we're in a branch delay slot. The "fetch" is done in the next block. + if (!FetchInstruction()) + break; + + // execute the instruction we previously fetched + ExecuteInstruction(); + + // next load delay + UpdateLoadDelay(); + + const bool branch = IsBranchInstruction(g_state.current_instruction); + if (g_state.exception_raised || (!branch && in_branch_delay_slot) || + IsExitBlockInstruction(g_state.current_instruction)) + { + break; + } + + in_branch_delay_slot = branch; + } +} + +} // namespace CodeCache + +namespace Recompiler::Thunks { + +bool InterpretInstruction() +{ + ExecuteInstruction(); + return g_state.exception_raised; +} + +void RaiseException(u32 epc, u32 ri_bits) +{ + const RaiseExceptionInfo ri{ri_bits}; + RaiseException(static_cast(ri.excode), epc, ri.BD, g_state.current_instruction_was_branch_taken, ri.CE); +} + +void RaiseAddressException(u32 address, bool store, bool branch) +{ + g_state.cop0_regs.BadVaddr = address; + if (branch) + RaiseException(Exception::AdEL, address, false, false, 0); + else + RaiseException(store ? Exception::AdES : Exception::AdEL); +} + +} // namespace Recompiler::Thunks + +} // namespace CPU \ No newline at end of file diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index b097f4dd4..148afdcfa 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -1,186 +1,105 @@ #pragma once #include "common/bitfield.h" #include "cpu_types.h" -#include "gte.h" +#include "gte_types.h" #include "types.h" #include #include class StateWrapper; -class Bus; -class System; - namespace CPU { -class CodeCache; - -namespace Recompiler { -class CodeGenerator; -class Thunks; -} // namespace Recompiler - -class Core +enum : VirtualMemoryAddress { -public: - static constexpr VirtualMemoryAddress RESET_VECTOR = UINT32_C(0xBFC00000); - static constexpr PhysicalMemoryAddress DCACHE_LOCATION = UINT32_C(0x1F800000); - static constexpr PhysicalMemoryAddress DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00); - static constexpr PhysicalMemoryAddress DCACHE_OFFSET_MASK = UINT32_C(0x000003FF); - static constexpr PhysicalMemoryAddress DCACHE_SIZE = UINT32_C(0x00000400); - - friend CodeCache; - friend Recompiler::CodeGenerator; - friend Recompiler::Thunks; - - Core(); - ~Core(); - - void Initialize(Bus* bus); - void Reset(); - bool DoState(StateWrapper& sw); - - void Execute(); - - ALWAYS_INLINE Bus* GetBus() const { return m_bus; } - - ALWAYS_INLINE const Registers& GetRegs() const { return m_regs; } - ALWAYS_INLINE Registers& GetRegs() { return m_regs; } - - ALWAYS_INLINE TickCount GetPendingTicks() const { return m_pending_ticks; } - ALWAYS_INLINE void ResetPendingTicks() { m_pending_ticks = 0; } - ALWAYS_INLINE void AddPendingTicks(TickCount ticks) { m_pending_ticks += ticks; } - - ALWAYS_INLINE TickCount GetDowncount() const { return m_downcount; } - ALWAYS_INLINE void SetDowncount(TickCount downcount) { m_downcount = downcount; } - - ALWAYS_INLINE const GTE::Core& GetCop2() const { return m_cop2; } - ALWAYS_INLINE GTE::Core& GetCop2() { return m_cop2; } - - // Sets the PC and flushes the pipeline. - void SetPC(u32 new_pc); - - // Memory reads variants which do not raise exceptions. - bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value); - bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); - bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value); - bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value); - bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); - bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value); - - // External IRQs - void SetExternalInterrupt(u8 bit); - void ClearExternalInterrupt(u8 bit); - -private: - template - TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& value); - - template - bool DoAlignmentCheck(VirtualMemoryAddress address); - - template - void DoScratchpadAccess(PhysicalMemoryAddress address, u32& value); - - bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value); - bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); - bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value); - bool WriteMemoryByte(VirtualMemoryAddress addr, u8 value); - bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); - bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value); - - // state helpers - ALWAYS_INLINE bool InUserMode() const { return m_cop0_regs.sr.KUc; } - ALWAYS_INLINE bool InKernelMode() const { return !m_cop0_regs.sr.KUc; } - - void DisassembleAndPrint(u32 addr); - void DisassembleAndLog(u32 addr); - void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_after); - - // Updates load delays - call after each instruction - ALWAYS_INLINE void UpdateLoadDelay() - { - // the old value is needed in case the delay slot instruction overwrites the same register - if (m_load_delay_reg != Reg::count) - m_regs.r[static_cast(m_load_delay_reg)] = m_load_delay_value; - - m_load_delay_reg = m_next_load_delay_reg; - m_load_delay_value = m_next_load_delay_value; - m_next_load_delay_reg = Reg::count; - } - - // Fetches the instruction at m_regs.npc - bool FetchInstruction(); - void ExecuteInstruction(); - void ExecuteCop0Instruction(); - void ExecuteCop2Instruction(); - void Branch(u32 target); - - // exceptions - u32 GetExceptionVector(Exception excode) const; - void RaiseException(Exception excode); - void RaiseException(Exception excode, u32 EPC, bool BD, bool BT, u8 CE); - bool HasPendingInterrupt(); - void DispatchInterrupt(); - - // clears pipeline of load/branch delays - void FlushPipeline(); - - // helper functions for registers which aren't writable - u32 ReadReg(Reg rs); - void WriteReg(Reg rd, u32 value); - - // helper for generating a load delay write - void WriteRegDelayed(Reg rd, u32 value); - - // write to cache control register - void WriteCacheControl(u32 value); - - // read/write cop0 regs - std::optional ReadCop0Reg(Cop0Reg reg); - void WriteCop0Reg(Cop0Reg reg, u32 value); - - Bus* m_bus = nullptr; - - // ticks the CPU has executed - TickCount m_pending_ticks = 0; - TickCount m_downcount = MAX_SLICE_SIZE; - - Registers m_regs = {}; - Cop0Registers m_cop0_regs = {}; - Instruction m_next_instruction = {}; - - // address of the instruction currently being executed - Instruction m_current_instruction = {}; - u32 m_current_instruction_pc = 0; - bool m_current_instruction_in_branch_delay_slot = false; - bool m_current_instruction_was_branch_taken = false; - bool m_next_instruction_is_branch_delay_slot = false; - bool m_branch_was_taken = false; - bool m_exception_raised = false; - bool m_interrupt_delay = false; - - // load delays - Reg m_load_delay_reg = Reg::count; - u32 m_load_delay_value = 0; - Reg m_next_load_delay_reg = Reg::count; - u32 m_next_load_delay_value = 0; - - u32 m_cache_control = 0; - System* m_system = nullptr; - - // data cache (used as scratchpad) - std::array m_dcache = {}; - - GTE::Core m_cop2; + RESET_VECTOR = UINT32_C(0xBFC00000) +}; +enum : PhysicalMemoryAddress +{ + DCACHE_LOCATION = UINT32_C(0x1F800000), + DCACHE_LOCATION_MASK = UINT32_C(0xFFFFFC00), + DCACHE_OFFSET_MASK = UINT32_C(0x000003FF), + DCACHE_SIZE = UINT32_C(0x00000400) }; -extern bool TRACE_EXECUTION; -extern bool LOG_EXECUTION; +struct State +{ + // ticks the CPU has executed + TickCount pending_ticks = 0; + TickCount downcount = MAX_SLICE_SIZE; + + Registers regs = {}; + Cop0Registers cop0_regs = {}; + Instruction next_instruction = {}; + + // address of the instruction currently being executed + Instruction current_instruction = {}; + u32 current_instruction_pc = 0; + bool current_instruction_in_branch_delay_slot = false; + bool current_instruction_was_branch_taken = false; + bool next_instruction_is_branch_delay_slot = false; + bool branch_was_taken = false; + bool exception_raised = false; + bool interrupt_delay = false; + bool frame_done = false; + + // load delays + Reg load_delay_reg = Reg::count; + u32 load_delay_value = 0; + Reg next_load_delay_reg = Reg::count; + u32 next_load_delay_value = 0; + + u32 cache_control = 0; + + // GTE registers are stored here so we can access them on ARM with a single instruction + GTE::Regs gte_regs = {}; + + // data cache (used as scratchpad) + std::array dcache = {}; +}; + +extern State g_state; + +void Initialize(); +void Shutdown(); +void Reset(); +bool DoState(StateWrapper& sw); + +/// Executes interpreter loop. +void Execute(); + +ALWAYS_INLINE Registers& GetRegs() { return g_state.regs; } + +ALWAYS_INLINE TickCount GetPendingTicks() { return g_state.pending_ticks; } +ALWAYS_INLINE void ResetPendingTicks() { g_state.pending_ticks = 0; } +ALWAYS_INLINE void AddPendingTicks(TickCount ticks) { g_state.pending_ticks += ticks; } + +// state helpers +ALWAYS_INLINE bool InUserMode() { return g_state.cop0_regs.sr.KUc; } +ALWAYS_INLINE bool InKernelMode() { return !g_state.cop0_regs.sr.KUc; } + +// Memory reads variants which do not raise exceptions. +bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value); +bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); +bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value); +bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value); +bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); +bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value); + +// External IRQs +void SetExternalInterrupt(u8 bit); +void ClearExternalInterrupt(u8 bit); +bool HasPendingInterrupt(); +void DispatchInterrupt(); + +void DisassembleAndPrint(u32 addr); +void DisassembleAndLog(u32 addr); +void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_after); // Write to CPU execution log file. void WriteToExecutionLog(const char* format, ...); -} // namespace CPU +extern bool TRACE_EXECUTION; +extern bool LOG_EXECUTION; -#include "cpu_core.inl" +} // namespace CPU diff --git a/src/core/cpu_core.inl b/src/core/cpu_core.inl deleted file mode 100644 index ffc5584cf..000000000 --- a/src/core/cpu_core.inl +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once -#include "common/align.h" -#include "bus.h" -#include "cpu_core.h" - -namespace CPU { - -template -TickCount Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value) -{ - switch (address >> 29) - { - case 0x00: // KUSEG 0M-512M - { - if constexpr (type == MemoryAccessType::Write) - { - if (m_cop0_regs.sr.Isc) - return 0; - } - - const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF); - if ((phys_addr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION) - { - DoScratchpadAccess(phys_addr, value); - return 0; - } - - return m_bus->DispatchAccess(phys_addr, value); - } - - case 0x01: // KUSEG 512M-1024M - case 0x02: // KUSEG 1024M-1536M - case 0x03: // KUSEG 1536M-2048M - { - // Above 512mb raises an exception. - return -1; - } - - case 0x04: // KSEG0 - physical memory cached - { - if constexpr (type == MemoryAccessType::Write) - { - if (m_cop0_regs.sr.Isc) - return 0; - } - - const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF); - if ((phys_addr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION) - { - DoScratchpadAccess(phys_addr, value); - return 0; - } - - return m_bus->DispatchAccess(phys_addr, value); - } - break; - - case 0x05: // KSEG1 - physical memory uncached - { - const PhysicalMemoryAddress phys_addr = address & UINT32_C(0x1FFFFFFF); - return m_bus->DispatchAccess(phys_addr, value); - } - break; - - case 0x06: // KSEG2 - case 0x07: // KSEG2 - { - if (address == 0xFFFE0130) - { - if constexpr (type == MemoryAccessType::Read) - value = m_cache_control; - else - WriteCacheControl(value); - - return 0; - } - else - { - return -1; - } - } - - default: - UnreachableCode(); - return false; - } -} - -template -bool CPU::Core::DoAlignmentCheck(VirtualMemoryAddress address) -{ - if constexpr (size == MemoryAccessSize::HalfWord) - { - if (Common::IsAlignedPow2(address, 2)) - return true; - } - else if constexpr (size == MemoryAccessSize::Word) - { - if (Common::IsAlignedPow2(address, 4)) - return true; - } - else - { - return true; - } - - m_cop0_regs.BadVaddr = address; - RaiseException(type == MemoryAccessType::Read ? Exception::AdEL : Exception::AdES); - return false; -} - -template -void CPU::Core::DoScratchpadAccess(PhysicalMemoryAddress address, u32& value) -{ - const PhysicalMemoryAddress cache_offset = address & DCACHE_OFFSET_MASK; - if constexpr (size == MemoryAccessSize::Byte) - { - if constexpr (type == MemoryAccessType::Read) - value = ZeroExtend32(m_dcache[cache_offset]); - else - m_dcache[cache_offset] = Truncate8(value); - } - else if constexpr (size == MemoryAccessSize::HalfWord) - { - if constexpr (type == MemoryAccessType::Read) - { - u16 temp; - std::memcpy(&temp, &m_dcache[cache_offset], sizeof(temp)); - value = ZeroExtend32(temp); - } - else - { - u16 temp = Truncate16(value); - std::memcpy(&m_dcache[cache_offset], &temp, sizeof(temp)); - } - } - else if constexpr (size == MemoryAccessSize::Word) - { - if constexpr (type == MemoryAccessType::Read) - std::memcpy(&value, &m_dcache[cache_offset], sizeof(value)); - else - std::memcpy(&m_dcache[cache_offset], &value, sizeof(value)); - } -} - -} // namespace CPU \ No newline at end of file diff --git a/src/core/cpu_disasm.cpp b/src/core/cpu_disasm.cpp index 9ae1d3021..6a797ace4 100644 --- a/src/core/cpu_disasm.cpp +++ b/src/core/cpu_disasm.cpp @@ -1,5 +1,6 @@ #include "cpu_disasm.h" #include "cpu_core.h" +#include "common/assert.h" #include namespace CPU { @@ -166,7 +167,7 @@ static const std::array, 5> s_cop_c static const std::array, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}}; -static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core* state, const char* format) +static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Registers* regs, const char* format) { dest->Clear(); @@ -185,10 +186,10 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core if (std::strncmp(str, "rs", 2) == 0) { dest->AppendString(GetRegName(inst.r.rs)); - if (state) + if (regs) { comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rs), - state->GetRegs().r[static_cast(inst.r.rs.GetValue())]); + regs->r[static_cast(inst.r.rs.GetValue())]); } str += 2; @@ -196,10 +197,10 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core else if (std::strncmp(str, "rt", 2) == 0) { dest->AppendString(GetRegName(inst.r.rt)); - if (state) + if (regs) { comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rt), - state->GetRegs().r[static_cast(inst.r.rt.GetValue())]); + regs->r[static_cast(inst.r.rt.GetValue())]); } str += 2; @@ -207,10 +208,10 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core else if (std::strncmp(str, "rd", 2) == 0) { dest->AppendString(GetRegName(inst.r.rd)); - if (state) + if (regs) { comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rd), - state->GetRegs().r[static_cast(inst.r.rd.GetValue())]); + regs->r[static_cast(inst.r.rd.GetValue())]); } str += 2; @@ -241,10 +242,10 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core { const s32 offset = static_cast(inst.i.imm_sext32()); dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs)); - if (state) + if (regs) { comment.AppendFormattedString("%saddr=0x%08X", comment.IsEmpty() ? "" : ", ", - state->GetRegs().r[static_cast(inst.i.rs.GetValue())] + offset); + regs->r[static_cast(inst.i.rs.GetValue())] + offset); } str += 8; @@ -291,14 +292,14 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Core } template -void FormatCopInstruction(String* dest, u32 pc, Core* state, const Instruction inst, +void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instruction inst, const std::pair* table, size_t table_size, T table_key) { for (size_t i = 0; i < table_size; i++) { if (table[i].first == table_key) { - FormatInstruction(dest, inst, pc, state, table[i].second); + FormatInstruction(dest, inst, pc, regs, table[i].second); return; } } @@ -306,13 +307,13 @@ void FormatCopInstruction(String* dest, u32 pc, Core* state, const Instruction i dest->Format("", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue()); } -void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state) +void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) { const Instruction inst{bits}; switch (inst.op) { case InstructionOp::funct: - FormatInstruction(dest, inst, pc, state, s_special_table[static_cast(inst.r.funct.GetValue())]); + FormatInstruction(dest, inst, pc, regs, s_special_table[static_cast(inst.r.funct.GetValue())]); return; case InstructionOp::cop0: @@ -322,7 +323,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state) { if (inst.cop.IsCommonInstruction()) { - FormatCopInstruction(dest, pc, state, inst, s_cop_common_table.data(), s_cop_common_table.size(), + FormatCopInstruction(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp()); } else @@ -331,7 +332,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state) { case InstructionOp::cop0: { - FormatCopInstruction(dest, pc, state, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op()); + FormatCopInstruction(dest, pc, regs, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op()); } break; @@ -355,14 +356,14 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state) const bool bgez = ConvertToBoolUnchecked(rt & u8(1)); const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1)); if (link) - FormatInstruction(dest, inst, pc, state, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel"); + FormatInstruction(dest, inst, pc, regs, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel"); else - FormatInstruction(dest, inst, pc, state, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel"); + FormatInstruction(dest, inst, pc, regs, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel"); } break; default: - FormatInstruction(dest, inst, pc, state, s_base_table[static_cast(inst.op.GetValue())]); + FormatInstruction(dest, inst, pc, regs, s_base_table[static_cast(inst.op.GetValue())]); break; } } diff --git a/src/core/cpu_disasm.h b/src/core/cpu_disasm.h index 5b11eebdf..c807f6c3c 100644 --- a/src/core/cpu_disasm.h +++ b/src/core/cpu_disasm.h @@ -3,7 +3,5 @@ #include "cpu_types.h" namespace CPU { -class Core; - -void DisassembleInstruction(String* dest, u32 pc, u32 bits, Core* state = nullptr); +void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs = nullptr); } // namespace CPU diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index af089e054..42523791a 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -2,6 +2,7 @@ #include "common/log.h" #include "cpu_core.h" #include "cpu_disasm.h" +#include "gte.h" Log_SetChannel(CPU::Recompiler); // TODO: Turn load+sext/zext into a single signed/unsigned load @@ -12,7 +13,7 @@ namespace CPU::Recompiler { u32 CodeGenerator::CalculateRegisterOffset(Reg reg) { - return u32(offsetof(Core, m_regs.r[0]) + (static_cast(reg) * sizeof(u32))); + return u32(offsetof(State, regs.r[0]) + (static_cast(reg) * sizeof(u32))); } bool CodeGenerator::CompileBlock(const CodeBlock* block, CodeBlock::HostCodePointer* out_host_code, @@ -802,7 +803,7 @@ void CodeGenerator::GenerateExceptionExit(const CodeBlockInstruction& cbi, Excep m_register_cache.FlushAllGuestRegisters(true, true); m_register_cache.FlushLoadDelay(true); - EmitFunctionCall(nullptr, &Thunks::RaiseException, m_register_cache.GetCPUPtr(), epc, ri_bits); + EmitFunctionCall(nullptr, &Thunks::RaiseException, epc, ri_bits); return; } @@ -814,7 +815,7 @@ void CodeGenerator::GenerateExceptionExit(const CodeBlockInstruction& cbi, Excep EmitBranch(GetCurrentFarCodePointer()); SwitchToFarCode(); - EmitFunctionCall(nullptr, &Thunks::RaiseException, m_register_cache.GetCPUPtr(), epc, ri_bits); + EmitFunctionCall(nullptr, &Thunks::RaiseException, epc, ri_bits); EmitExceptionExit(); SwitchToNearCode(); @@ -825,7 +826,7 @@ void CodeGenerator::GenerateExceptionExit(const CodeBlockInstruction& cbi, Excep void CodeGenerator::BlockPrologue() { - EmitStoreCPUStructField(offsetof(Core, m_exception_raised), Value::FromConstantU8(0)); + EmitStoreCPUStructField(offsetof(State, exception_raised), Value::FromConstantU8(0)); // we don't know the state of the last block, so assume load delays might be in progress // TODO: Pull load delay into register cache @@ -859,21 +860,21 @@ void CodeGenerator::InstructionPrologue(const CodeBlockInstruction& cbi, TickCou if (m_branch_was_taken_dirty) { Value temp = m_register_cache.AllocateScratch(RegSize_8); - EmitLoadCPUStructField(temp.host_reg, RegSize_8, offsetof(Core, m_branch_was_taken)); - EmitStoreCPUStructField(offsetof(Core, m_current_instruction_was_branch_taken), temp); - EmitStoreCPUStructField(offsetof(Core, m_branch_was_taken), Value::FromConstantU8(0)); + EmitLoadCPUStructField(temp.host_reg, RegSize_8, offsetof(State, branch_was_taken)); + EmitStoreCPUStructField(offsetof(State, current_instruction_was_branch_taken), temp); + EmitStoreCPUStructField(offsetof(State, branch_was_taken), Value::FromConstantU8(0)); m_current_instruction_was_branch_taken_dirty = true; m_branch_was_taken_dirty = false; } else if (m_current_instruction_was_branch_taken_dirty) { - EmitStoreCPUStructField(offsetof(Core, m_current_instruction_was_branch_taken), Value::FromConstantU8(0)); + EmitStoreCPUStructField(offsetof(State, current_instruction_was_branch_taken), Value::FromConstantU8(0)); m_current_instruction_was_branch_taken_dirty = false; } if (m_current_instruction_in_branch_delay_slot_dirty && !cbi.is_branch_delay_slot) { - EmitStoreCPUStructField(offsetof(Core, m_current_instruction_in_branch_delay_slot), Value::FromConstantU8(0)); + EmitStoreCPUStructField(offsetof(State, current_instruction_in_branch_delay_slot), Value::FromConstantU8(0)); m_current_instruction_in_branch_delay_slot_dirty = false; } @@ -894,7 +895,7 @@ void CodeGenerator::InstructionPrologue(const CodeBlockInstruction& cbi, TickCou if (cbi.is_branch_delay_slot) { // m_current_instruction_in_branch_delay_slot = true - EmitStoreCPUStructField(offsetof(Core, m_current_instruction_in_branch_delay_slot), Value::FromConstantU8(1)); + EmitStoreCPUStructField(offsetof(State, current_instruction_in_branch_delay_slot), Value::FromConstantU8(1)); m_current_instruction_in_branch_delay_slot_dirty = true; } @@ -931,7 +932,7 @@ void CodeGenerator::AddPendingCycles(bool commit) if (m_delayed_cycles_add == 0) return; - EmitAddCPUStructField(offsetof(Core, m_pending_ticks), Value::FromConstantU32(m_delayed_cycles_add)); + EmitAddCPUStructField(offsetof(State, pending_ticks), Value::FromConstantU32(m_delayed_cycles_add)); if (commit) m_delayed_cycles_add = 0; @@ -939,7 +940,7 @@ void CodeGenerator::AddPendingCycles(bool commit) void CodeGenerator::SetCurrentInstructionPC(const CodeBlockInstruction& cbi) { - EmitStoreCPUStructField(offsetof(Core, m_current_instruction_pc), Value::FromConstantU32(cbi.pc)); + EmitStoreCPUStructField(offsetof(State, current_instruction_pc), Value::FromConstantU32(cbi.pc)); } bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi) @@ -954,19 +955,19 @@ bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi) m_register_cache.WriteLoadDelayToCPU(true); } - EmitStoreCPUStructField(offsetof(Core, m_current_instruction.bits), Value::FromConstantU32(cbi.instruction.bits)); + EmitStoreCPUStructField(offsetof(State, current_instruction.bits), Value::FromConstantU32(cbi.instruction.bits)); // emit the function call if (CanInstructionTrap(cbi.instruction, m_block->key.user_mode)) { // TODO: Use carry flag or something here too Value return_value = m_register_cache.AllocateScratch(RegSize_8); - EmitFunctionCall(&return_value, &Thunks::InterpretInstruction, m_register_cache.GetCPUPtr()); + EmitFunctionCall(&return_value, &Thunks::InterpretInstruction); EmitExceptionExitOnBool(return_value); } else { - EmitFunctionCall(nullptr, &Thunks::InterpretInstruction, m_register_cache.GetCPUPtr()); + EmitFunctionCall(nullptr, &Thunks::InterpretInstruction); } m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch_instruction; @@ -1389,8 +1390,8 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi) EmitBindLabel(&branch_okay); SwitchToFarCode(); - EmitFunctionCall(nullptr, &Thunks::RaiseAddressException, m_register_cache.GetCPUPtr(), branch_target, - Value::FromConstantU8(0), Value::FromConstantU8(1)); + EmitFunctionCall(nullptr, &Thunks::RaiseAddressException, branch_target, Value::FromConstantU8(0), + Value::FromConstantU8(1)); EmitExceptionExit(); SwitchToNearCode(); @@ -1550,53 +1551,53 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi) switch (reg) { case Cop0Reg::BPC: - offset = offsetof(Core, m_cop0_regs.BPC); + offset = offsetof(State, cop0_regs.BPC); break; case Cop0Reg::BPCM: - offset = offsetof(Core, m_cop0_regs.BPCM); + offset = offsetof(State, cop0_regs.BPCM); break; case Cop0Reg::BDA: - offset = offsetof(Core, m_cop0_regs.BDA); + offset = offsetof(State, cop0_regs.BDA); break; case Cop0Reg::BDAM: - offset = offsetof(Core, m_cop0_regs.BDAM); + offset = offsetof(State, cop0_regs.BDAM); break; case Cop0Reg::DCIC: - offset = offsetof(Core, m_cop0_regs.dcic.bits); + offset = offsetof(State, cop0_regs.dcic.bits); write_mask = Cop0Registers::DCIC::WRITE_MASK; break; case Cop0Reg::JUMPDEST: - offset = offsetof(Core, m_cop0_regs.TAR); + offset = offsetof(State, cop0_regs.TAR); write_mask = 0; break; case Cop0Reg::BadVaddr: - offset = offsetof(Core, m_cop0_regs.BadVaddr); + offset = offsetof(State, cop0_regs.BadVaddr); write_mask = 0; break; case Cop0Reg::SR: - offset = offsetof(Core, m_cop0_regs.sr.bits); + offset = offsetof(State, cop0_regs.sr.bits); write_mask = Cop0Registers::SR::WRITE_MASK; break; case Cop0Reg::CAUSE: - offset = offsetof(Core, m_cop0_regs.cause.bits); + offset = offsetof(State, cop0_regs.cause.bits); write_mask = Cop0Registers::CAUSE::WRITE_MASK; break; case Cop0Reg::EPC: - offset = offsetof(Core, m_cop0_regs.EPC); + offset = offsetof(State, cop0_regs.EPC); write_mask = 0; break; case Cop0Reg::PRID: - offset = offsetof(Core, m_cop0_regs.PRID); + offset = offsetof(State, cop0_regs.PRID); write_mask = 0; break; @@ -1638,8 +1639,8 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi) // m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0) LabelType no_interrupt; - EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(Core, m_cop0_regs.sr.bits)); - EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(Core, m_cop0_regs.cause.bits)); + EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(State, cop0_regs.sr.bits)); + EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(State, cop0_regs.cause.bits)); EmitBranchIfBitClear(sr_value.host_reg, sr_value.size, 0, &no_interrupt); EmitAnd(sr_value.host_reg, sr_value.host_reg, cause_value); EmitTest(sr_value.host_reg, Value::FromConstantU32(0xFF00)); @@ -1680,7 +1681,7 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi) // shift mode bits right two, preserving upper bits static constexpr u32 mode_bits_mask = UINT32_C(0b1111); Value sr = m_register_cache.AllocateScratch(RegSize_32); - EmitLoadCPUStructField(sr.host_reg, RegSize_32, offsetof(Core, m_cop0_regs.sr.bits)); + EmitLoadCPUStructField(sr.host_reg, RegSize_32, offsetof(State, cop0_regs.sr.bits)); { Value new_mode_bits = m_register_cache.AllocateScratch(RegSize_32); EmitShr(new_mode_bits.host_reg, sr.host_reg, new_mode_bits.size, Value::FromConstantU32(2)); @@ -1689,7 +1690,7 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi) EmitOr(sr.host_reg, sr.host_reg, new_mode_bits); } - EmitStoreCPUStructField(offsetof(Core, m_cop0_regs.sr.bits), sr); + EmitStoreCPUStructField(offsetof(State, cop0_regs.sr.bits), sr); InstructionEpilogue(cbi); return true; @@ -1717,13 +1718,13 @@ Value CodeGenerator::DoGTERegisterRead(u32 index) case 28: // IRGB case 29: // ORGB { - EmitFunctionCall(&value, &Thunks::ReadGTERegister, m_register_cache.GetCPUPtr(), Value::FromConstantU32(index)); + EmitFunctionCall(&value, >E::ReadRegister, Value::FromConstantU32(index)); } break; default: { - EmitLoadCPUStructField(value.host_reg, RegSize_32, offsetof(Core, m_cop2.m_regs.r32[index])); + EmitLoadCPUStructField(value.host_reg, RegSize_32, offsetof(State, gte_regs.r32[index])); } break; } @@ -1752,7 +1753,7 @@ void CodeGenerator::DoGTERegisterWrite(u32 index, const Value& value) { // sign-extend z component of vector registers Value temp = ConvertValueSize(value.ViewAsSize(RegSize_16), RegSize_32, true); - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[index]), temp); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[index]), temp); return; } break; @@ -1765,7 +1766,7 @@ void CodeGenerator::DoGTERegisterWrite(u32 index, const Value& value) { // zero-extend unsigned values Value temp = ConvertValueSize(value.ViewAsSize(RegSize_16), RegSize_32, false); - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[index]), temp); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[index]), temp); return; } break; @@ -1776,15 +1777,15 @@ void CodeGenerator::DoGTERegisterWrite(u32 index, const Value& value) Value temp = m_register_cache.AllocateScratch(RegSize_32); // SXY0 <- SXY1 - EmitLoadCPUStructField(temp.host_reg, RegSize_32, offsetof(Core, m_cop2.m_regs.r32[13])); - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[12]), temp); + EmitLoadCPUStructField(temp.host_reg, RegSize_32, offsetof(State, gte_regs.r32[13])); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[12]), temp); // SXY1 <- SXY2 - EmitLoadCPUStructField(temp.host_reg, RegSize_32, offsetof(Core, m_cop2.m_regs.r32[14])); - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[13]), temp); + EmitLoadCPUStructField(temp.host_reg, RegSize_32, offsetof(State, gte_regs.r32[14])); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[13]), temp); // SXY2 <- SXYP - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[14]), value); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[14]), value); return; } break; @@ -1793,8 +1794,7 @@ void CodeGenerator::DoGTERegisterWrite(u32 index, const Value& value) case 30: // LZCS case 63: // FLAG { - EmitFunctionCall(nullptr, &Thunks::WriteGTERegister, m_register_cache.GetCPUPtr(), Value::FromConstantU32(index), - value); + EmitFunctionCall(nullptr, >E::WriteRegister, Value::FromConstantU32(index), value); return; } @@ -1808,7 +1808,7 @@ void CodeGenerator::DoGTERegisterWrite(u32 index, const Value& value) default: { // written as-is, 2x16 or 1x32 bits - EmitStoreCPUStructField(offsetof(Core, m_cop2.m_regs.r32[index]), value); + EmitStoreCPUStructField(offsetof(State, gte_regs.r32[index]), value); return; } } @@ -1878,7 +1878,7 @@ bool CodeGenerator::Compile_cop2(const CodeBlockInstruction& cbi) InstructionPrologue(cbi, 1); Value instruction_bits = Value::FromConstantU32(cbi.instruction.bits & GTE::Instruction::REQUIRED_BITS_MASK); - EmitFunctionCall(nullptr, &Thunks::ExecuteGTEInstruction, m_register_cache.GetCPUPtr(), instruction_bits); + EmitFunctionCall(nullptr, GTE::GetInstructionImpl(cbi.instruction.bits), instruction_bits); InstructionEpilogue(cbi); return true; diff --git a/src/core/cpu_recompiler_code_generator.h b/src/core/cpu_recompiler_code_generator.h index 2c932188b..d0b9b4236 100644 --- a/src/core/cpu_recompiler_code_generator.h +++ b/src/core/cpu_recompiler_code_generator.h @@ -16,7 +16,7 @@ namespace CPU::Recompiler { class CodeGenerator { public: - CodeGenerator(Core* cpu, JitCodeBuffer* code_buffer, const ASMFunctions& asm_functions); + CodeGenerator(JitCodeBuffer* code_buffer); ~CodeGenerator(); static u32 CalculateRegisterOffset(Reg reg); @@ -62,6 +62,8 @@ public: void EmitLoadCPUStructField(HostReg host_reg, RegSize size, u32 offset); void EmitStoreCPUStructField(u32 offset, const Value& value); void EmitAddCPUStructField(u32 offset, const Value& value); + void EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr); + void EmitStoreGlobal(void* ptr, const Value& value); // Automatically generates an exception handler. Value EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const Value& address, RegSize size); @@ -188,9 +190,7 @@ private: bool Compile_cop0(const CodeBlockInstruction& cbi); bool Compile_cop2(const CodeBlockInstruction& cbi); - Core* m_cpu; JitCodeBuffer* m_code_buffer; - const ASMFunctions& m_asm_functions; const CodeBlock* m_block = nullptr; const CodeBlockInstruction* m_block_start = nullptr; const CodeBlockInstruction* m_block_end = nullptr; diff --git a/src/core/cpu_recompiler_code_generator_aarch64.cpp b/src/core/cpu_recompiler_code_generator_aarch64.cpp index 8bd676439..eb0e2431f 100644 --- a/src/core/cpu_recompiler_code_generator_aarch64.cpp +++ b/src/core/cpu_recompiler_code_generator_aarch64.cpp @@ -1,7 +1,9 @@ +#include "common/align.h" +#include "common/assert.h" #include "common/log.h" +#include "cpu_core.h" #include "cpu_recompiler_code_generator.h" #include "cpu_recompiler_thunks.h" -#include "cpu_core.h" Log_SetChannel(CPU::Recompiler); namespace a64 = vixl::aarch64; @@ -21,10 +23,7 @@ constexpr u64 FUNCTION_CALLER_SAVED_SPACE_RESERVE = 144; // 18 registers -> 224 constexpr u64 FUNCTION_STACK_SIZE = FUNCTION_CALLEE_SAVED_SPACE_RESERVE + FUNCTION_CALLER_SAVED_SPACE_RESERVE + FUNCTION_CALL_SHADOW_SPACE; -static const a64::WRegister GetHostReg8(HostReg reg) -{ - return a64::WRegister(reg); -} +static const a64::WRegister GetHostReg8(HostReg reg) { return a64::WRegister(reg); } static const a64::WRegister GetHostReg8(const Value& value) { @@ -32,10 +31,7 @@ static const a64::WRegister GetHostReg8(const Value& value) return a64::WRegister(value.host_reg); } -static const a64::WRegister GetHostReg16(HostReg reg) -{ - return a64::WRegister(reg); -} +static const a64::WRegister GetHostReg16(HostReg reg) { return a64::WRegister(reg); } static const a64::WRegister GetHostReg16(const Value& value) { @@ -43,10 +39,7 @@ static const a64::WRegister GetHostReg16(const Value& value) return a64::WRegister(value.host_reg); } -static const a64::WRegister GetHostReg32(HostReg reg) -{ - return a64::WRegister(reg); -} +static const a64::WRegister GetHostReg32(HostReg reg) { return a64::WRegister(reg); } static const a64::WRegister GetHostReg32(const Value& value) { @@ -54,10 +47,7 @@ static const a64::WRegister GetHostReg32(const Value& value) return a64::WRegister(value.host_reg); } -static const a64::XRegister GetHostReg64(HostReg reg) -{ - return a64::XRegister(reg); -} +static const a64::XRegister GetHostReg64(HostReg reg) { return a64::XRegister(reg); } static const a64::XRegister GetHostReg64(const Value& value) { @@ -65,13 +55,10 @@ static const a64::XRegister GetHostReg64(const Value& value) return a64::XRegister(value.host_reg); } -static const a64::XRegister GetCPUPtrReg() -{ - return GetHostReg64(RCPUPTR); -} +static const a64::XRegister GetCPUPtrReg() { return GetHostReg64(RCPUPTR); } -CodeGenerator::CodeGenerator(Core* cpu, JitCodeBuffer* code_buffer, const ASMFunctions& asm_functions) - : m_cpu(cpu), m_code_buffer(code_buffer), m_asm_functions(asm_functions), m_register_cache(*this), +CodeGenerator::CodeGenerator(JitCodeBuffer* code_buffer) + : m_code_buffer(code_buffer), m_register_cache(*this), m_near_emitter(static_cast(code_buffer->GetFreeCodePointer()), code_buffer->GetFreeCodeSpace(), a64::PositionDependentCode), m_far_emitter(static_cast(code_buffer->GetFreeFarCodePointer()), code_buffer->GetFreeFarCodeSpace(), @@ -111,10 +98,7 @@ const char* CodeGenerator::GetHostRegName(HostReg reg, RegSize size /*= HostPoin } } -void CodeGenerator::AlignCodeBuffer(JitCodeBuffer* code_buffer) -{ - code_buffer->Align(16, 0x90); -} +void CodeGenerator::AlignCodeBuffer(JitCodeBuffer* code_buffer) { code_buffer->Align(16, 0x90); } void CodeGenerator::InitHostRegs() { @@ -127,15 +111,9 @@ void CodeGenerator::InitHostRegs() m_register_cache.SetCPUPtrHostReg(RCPUPTR); } -void CodeGenerator::SwitchToFarCode() -{ - m_emit = &m_far_emitter; -} +void CodeGenerator::SwitchToFarCode() { m_emit = &m_far_emitter; } -void CodeGenerator::SwitchToNearCode() -{ - m_emit = &m_near_emitter; -} +void CodeGenerator::SwitchToNearCode() { m_emit = &m_near_emitter; } void* CodeGenerator::GetCurrentNearCodePointer() const { @@ -168,10 +146,10 @@ void CodeGenerator::EmitBeginBlock() const bool link_reg_allocated = m_register_cache.AllocateHostReg(30); DebugAssert(link_reg_allocated); - // Store the CPU struct pointer. + // Store the CPU struct pointer. TODO: make this better. const bool cpu_reg_allocated = m_register_cache.AllocateHostReg(RCPUPTR); DebugAssert(cpu_reg_allocated); - m_emit->Mov(GetCPUPtrReg(), GetHostReg64(RARG1)); + m_emit->Mov(GetCPUPtrReg(), reinterpret_cast(&g_state)); } void CodeGenerator::EmitEndBlock() @@ -904,9 +882,13 @@ u32 CodeGenerator::PrepareStackForCall() return 0; } -void CodeGenerator::RestoreStackAfterCall(u32 adjust_size) +void CodeGenerator::RestoreStackAfterCall(u32 adjust_size) { m_register_cache.PopCallerSavedRegisters(); } + +static s64 GetBranchDisplacement(const void* current, const void* target) { - m_register_cache.PopCallerSavedRegisters(); + Assert(Common::IsAlignedPow2(reinterpret_cast(current), 4)); + Assert(Common::IsAlignedPow2(reinterpret_cast(target), 4)); + return static_cast((reinterpret_cast(target) - reinterpret_cast(current)) >> 2); } void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr) @@ -914,22 +896,25 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr) if (return_value) return_value->Discard(); - // must be allocated before the stack push - Value temp = m_register_cache.AllocateScratch(RegSize_64); - // shadow space allocate const u32 adjust_size = PrepareStackForCall(); // actually call the function - m_emit->Mov(GetHostReg64(temp), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(temp)); + const s64 displacement = GetBranchDisplacement(GetCurrentCodePointer(), ptr); + const bool use_blr = !vixl::IsInt26(displacement); + if (use_blr) + { + m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RRETURN)); + } + else + { + m_emit->bl(displacement); + } // shadow space release RestoreStackAfterCall(adjust_size); - // must happen after the stack push - temp.ReleaseAndClear(); - // copy out return value if requested if (return_value) { @@ -943,9 +928,6 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co if (return_value) return_value->Discard(); - // must be allocated before the stack push - Value temp = m_register_cache.AllocateScratch(RegSize_64); - // shadow space allocate const u32 adjust_size = PrepareStackForCall(); @@ -953,15 +935,21 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co EmitCopyValue(RARG1, arg1); // actually call the function - m_emit->Mov(GetHostReg64(temp), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(temp)); + const s64 displacement = GetBranchDisplacement(GetCurrentCodePointer(), ptr); + const bool use_blr = !vixl::IsInt26(displacement); + if (use_blr) + { + m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RRETURN)); + } + else + { + m_emit->bl(displacement); + } // shadow space release RestoreStackAfterCall(adjust_size); - // must happen after the stack push - temp.ReleaseAndClear(); - // copy out return value if requested if (return_value) { @@ -975,9 +963,6 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co if (return_value) return_value->Discard(); - // must be allocated before the stack push - Value temp = m_register_cache.AllocateScratch(RegSize_64); - // shadow space allocate const u32 adjust_size = PrepareStackForCall(); @@ -986,15 +971,21 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co EmitCopyValue(RARG2, arg2); // actually call the function - m_emit->Mov(GetHostReg64(temp), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(temp)); + const s64 displacement = GetBranchDisplacement(GetCurrentCodePointer(), ptr); + const bool use_blr = !vixl::IsInt26(displacement); + if (use_blr) + { + m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RRETURN)); + } + else + { + m_emit->bl(displacement); + } // shadow space release RestoreStackAfterCall(adjust_size); - // must happen after the stack push - temp.ReleaseAndClear(); - // copy out return value if requested if (return_value) { @@ -1009,9 +1000,6 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co if (return_value) m_register_cache.DiscardHostReg(return_value->GetHostRegister()); - // must be allocated before the stack push - Value temp = m_register_cache.AllocateScratch(RegSize_64); - // shadow space allocate const u32 adjust_size = PrepareStackForCall(); @@ -1021,15 +1009,21 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co EmitCopyValue(RARG3, arg3); // actually call the function - m_emit->Mov(GetHostReg64(temp), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(temp)); + const s64 displacement = GetBranchDisplacement(GetCurrentCodePointer(), ptr); + const bool use_blr = !vixl::IsInt26(displacement); + if (use_blr) + { + m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RRETURN)); + } + else + { + m_emit->bl(displacement); + } // shadow space release RestoreStackAfterCall(adjust_size); - // must happen after the stack push - temp.ReleaseAndClear(); - // copy out return value if requested if (return_value) { @@ -1044,8 +1038,6 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co if (return_value) return_value->Discard(); - // must be allocated before the stack push - Value temp = m_register_cache.AllocateScratch(RegSize_64); // shadow space allocate const u32 adjust_size = PrepareStackForCall(); @@ -1057,15 +1049,21 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr, co EmitCopyValue(RARG4, arg4); // actually call the function - m_emit->Mov(GetHostReg64(temp), reinterpret_cast(ptr)); - m_emit->Blr(GetHostReg64(temp)); + const s64 displacement = GetBranchDisplacement(GetCurrentCodePointer(), ptr); + const bool use_blr = !vixl::IsInt26(displacement); + if (use_blr) + { + m_emit->Mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->Blr(GetHostReg64(RRETURN)); + } + else + { + m_emit->bl(displacement); + } // shadow space release RestoreStackAfterCall(adjust_size); - // must happen after the stack push - temp.ReleaseAndClear(); - // copy out return value if requested if (return_value) { @@ -1222,15 +1220,15 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const switch (size) { case RegSize_8: - EmitFunctionCall(&result, &Thunks::ReadMemoryByte, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryByte, pc, address); break; case RegSize_16: - EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, pc, address); break; case RegSize_32: - EmitFunctionCall(&result, &Thunks::ReadMemoryWord, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryWord, pc, address); break; default: @@ -1285,15 +1283,15 @@ void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const switch (value.size) { case RegSize_8: - EmitFunctionCall(&result, &Thunks::WriteMemoryByte, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryByte, pc, address, value); break; case RegSize_16: - EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, pc, address, value); break; case RegSize_32: - EmitFunctionCall(&result, &Thunks::WriteMemoryWord, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryWord, pc, address, value); break; default: @@ -1316,14 +1314,18 @@ void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const m_register_cache.PopState(); } +void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr) { Panic("Not implemented"); } + +void CodeGenerator::EmitStoreGlobal(void* ptr, const Value& value) { Panic("Not implemented"); } + void CodeGenerator::EmitFlushInterpreterLoadDelay() { Value reg = m_register_cache.AllocateScratch(RegSize_32); Value value = m_register_cache.AllocateScratch(RegSize_32); - const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(Core, m_load_delay_reg)); - const a64::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(Core, m_load_delay_value)); - const a64::MemOperand regs_base(GetCPUPtrReg(), offsetof(Core, m_regs.r[0])); + const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg)); + const a64::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(State, load_delay_value)); + const a64::MemOperand regs_base(GetCPUPtrReg(), offsetof(State, regs.r[0])); a64::Label skip_flush; @@ -1339,7 +1341,7 @@ void CodeGenerator::EmitFlushInterpreterLoadDelay() // reg = offset(r[0] + reg << 2) m_emit->Lsl(GetHostReg32(reg), GetHostReg32(reg), 2); - m_emit->Add(GetHostReg32(reg), GetHostReg32(reg), offsetof(Core, m_regs.r[0])); + m_emit->Add(GetHostReg32(reg), GetHostReg32(reg), offsetof(State, regs.r[0])); // r[reg] = value m_emit->Str(GetHostReg32(value), a64::MemOperand(GetCPUPtrReg(), GetHostReg32(reg))); @@ -1356,10 +1358,10 @@ void CodeGenerator::EmitMoveNextInterpreterLoadDelay() Value reg = m_register_cache.AllocateScratch(RegSize_32); Value value = m_register_cache.AllocateScratch(RegSize_32); - const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(Core, m_load_delay_reg)); - const a64::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(Core, m_load_delay_value)); - const a64::MemOperand next_load_delay_reg(GetCPUPtrReg(), offsetof(Core, m_next_load_delay_reg)); - const a64::MemOperand next_load_delay_value(GetCPUPtrReg(), offsetof(Core, m_next_load_delay_value)); + const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg)); + const a64::MemOperand load_delay_value(GetCPUPtrReg(), offsetof(State, load_delay_value)); + const a64::MemOperand next_load_delay_reg(GetCPUPtrReg(), offsetof(State, next_load_delay_reg)); + const a64::MemOperand next_load_delay_value(GetCPUPtrReg(), offsetof(State, next_load_delay_value)); m_emit->Ldrb(GetHostReg32(reg), next_load_delay_reg); m_emit->Ldr(GetHostReg32(value), next_load_delay_value); @@ -1374,7 +1376,7 @@ void CodeGenerator::EmitCancelInterpreterLoadDelayForReg(Reg reg) if (!m_load_delay_dirty) return; - const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(Core, m_load_delay_reg)); + const a64::MemOperand load_delay_reg(GetCPUPtrReg(), offsetof(State, load_delay_reg)); Value temp = m_register_cache.AllocateScratch(RegSize_8); a64::Label skip_cancel; @@ -1632,11 +1634,6 @@ void CodeGenerator::EmitBranchIfBitClear(HostReg reg, RegSize size, u8 bit, Labe } } -void CodeGenerator::EmitBindLabel(LabelType* label) -{ - m_emit->Bind(label); -} - -void ASMFunctions::Generate(JitCodeBuffer* code_buffer) {} +void CodeGenerator::EmitBindLabel(LabelType* label) { m_emit->Bind(label); } } // namespace CPU::Recompiler diff --git a/src/core/cpu_recompiler_code_generator_generic.cpp b/src/core/cpu_recompiler_code_generator_generic.cpp index 22ac526df..b652cb24b 100644 --- a/src/core/cpu_recompiler_code_generator_generic.cpp +++ b/src/core/cpu_recompiler_code_generator_generic.cpp @@ -17,8 +17,8 @@ void CodeGenerator::EmitStoreGuestRegister(Reg guest_reg, const Value& value) void CodeGenerator::EmitStoreInterpreterLoadDelay(Reg reg, const Value& value) { DebugAssert(value.size == RegSize_32 && value.IsInHostRegister()); - EmitStoreCPUStructField(offsetof(Core, m_load_delay_reg), Value::FromConstantU8(static_cast(reg))); - EmitStoreCPUStructField(offsetof(Core, m_load_delay_value), value); + EmitStoreCPUStructField(offsetof(State, load_delay_reg), Value::FromConstantU8(static_cast(reg))); + EmitStoreCPUStructField(offsetof(State, load_delay_value), value); m_load_delay_dirty = true; } diff --git a/src/core/cpu_recompiler_code_generator_x64.cpp b/src/core/cpu_recompiler_code_generator_x64.cpp index 1039d2b04..a69b49b85 100644 --- a/src/core/cpu_recompiler_code_generator_x64.cpp +++ b/src/core/cpu_recompiler_code_generator_x64.cpp @@ -1,6 +1,7 @@ #include "cpu_core.h" #include "cpu_recompiler_code_generator.h" #include "cpu_recompiler_thunks.h" +#include "common/align.h" namespace CPU::Recompiler { @@ -73,8 +74,8 @@ static const Xbyak::Reg64 GetCPUPtrReg() return GetHostReg64(RCPUPTR); } -CodeGenerator::CodeGenerator(Core* cpu, JitCodeBuffer* code_buffer, const ASMFunctions& asm_functions) - : m_cpu(cpu), m_code_buffer(code_buffer), m_asm_functions(asm_functions), m_register_cache(*this), +CodeGenerator::CodeGenerator(JitCodeBuffer* code_buffer) + : m_code_buffer(code_buffer), m_register_cache(*this), m_near_emitter(code_buffer->GetFreeCodeSpace(), code_buffer->GetFreeCodePointer()), m_far_emitter(code_buffer->GetFreeFarCodeSpace(), code_buffer->GetFreeFarCodePointer()), m_emit(&m_near_emitter) { @@ -187,7 +188,7 @@ void CodeGenerator::EmitBeginBlock() // Store the CPU struct pointer. const bool cpu_reg_allocated = m_register_cache.AllocateHostReg(RCPUPTR); DebugAssert(cpu_reg_allocated); - m_emit->mov(GetCPUPtrReg(), GetHostReg64(RARG1)); + m_emit->mov(GetCPUPtrReg(), reinterpret_cast(&g_state)); } void CodeGenerator::EmitEndBlock() @@ -1305,8 +1306,15 @@ void CodeGenerator::EmitFunctionCallPtr(Value* return_value, const void* ptr) const u32 adjust_size = PrepareStackForCall(); // actually call the function - m_emit->mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); - m_emit->call(GetHostReg64(RRETURN)); + if (Xbyak::inner::IsInInt32(reinterpret_cast(ptr) - reinterpret_cast(m_emit->getCurr()))) + { + m_emit->call(ptr); + } + else + { + m_emit->mov(GetHostReg64(RRETURN), reinterpret_cast(ptr)); + m_emit->call(GetHostReg64(RRETURN)); + } // shadow space release RestoreStackAfterCall(adjust_size); @@ -1645,15 +1653,15 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const switch (size) { case RegSize_8: - EmitFunctionCall(&result, &Thunks::ReadMemoryByte, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryByte, pc, address); break; case RegSize_16: - EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, pc, address); break; case RegSize_32: - EmitFunctionCall(&result, &Thunks::ReadMemoryWord, m_register_cache.GetCPUPtr(), pc, address); + EmitFunctionCall(&result, &Thunks::ReadMemoryWord, pc, address); break; default: @@ -1706,15 +1714,15 @@ void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const switch (value.size) { case RegSize_8: - EmitFunctionCall(&result, &Thunks::WriteMemoryByte, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryByte, pc, address, value); break; case RegSize_16: - EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, pc, address, value); break; case RegSize_32: - EmitFunctionCall(&result, &Thunks::WriteMemoryWord, m_register_cache.GetCPUPtr(), pc, address, value); + EmitFunctionCall(&result, &Thunks::WriteMemoryWord, pc, address, value); break; default: @@ -1735,14 +1743,208 @@ void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const m_register_cache.PopState(); } +void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr) +{ + const s64 displacement = + static_cast(reinterpret_cast(ptr) - reinterpret_cast(m_emit->getCurr())) + 2; + if (Xbyak::inner::IsInInt32(static_cast(displacement))) + { + switch (size) + { + case RegSize_8: + m_emit->mov(GetHostReg8(host_reg), m_emit->byte[m_emit->rip + ptr]); + break; + + case RegSize_16: + m_emit->mov(GetHostReg16(host_reg), m_emit->word[m_emit->rip + ptr]); + break; + + case RegSize_32: + m_emit->mov(GetHostReg32(host_reg), m_emit->dword[m_emit->rip + ptr]); + break; + + case RegSize_64: + m_emit->mov(GetHostReg64(host_reg), m_emit->qword[m_emit->rip + ptr]); + break; + + default: + { + UnreachableCode(); + } + break; + } + } + else + { + Value temp = m_register_cache.AllocateScratch(RegSize_64); + m_emit->mov(GetHostReg64(temp), reinterpret_cast(ptr)); + switch (size) + { + case RegSize_8: + m_emit->mov(GetHostReg8(host_reg), m_emit->byte[GetHostReg64(temp)]); + break; + + case RegSize_16: + m_emit->mov(GetHostReg16(host_reg), m_emit->word[GetHostReg64(temp)]); + break; + + case RegSize_32: + m_emit->mov(GetHostReg32(host_reg), m_emit->dword[GetHostReg64(temp)]); + break; + + case RegSize_64: + m_emit->mov(GetHostReg64(host_reg), m_emit->qword[GetHostReg64(temp)]); + break; + + default: + { + UnreachableCode(); + } + break; + } + } +} + +void CodeGenerator::EmitStoreGlobal(void* ptr, const Value& value) +{ + DebugAssert(value.IsInHostRegister() || value.IsConstant()); + + const s64 displacement = + static_cast(reinterpret_cast(ptr) - reinterpret_cast(m_emit->getCurr())); + if (Xbyak::inner::IsInInt32(static_cast(displacement))) + { + switch (value.size) + { + case RegSize_8: + { + if (value.IsConstant()) + m_emit->mov(m_emit->byte[m_emit->rip + ptr], value.constant_value); + else + m_emit->mov(m_emit->byte[m_emit->rip + ptr], GetHostReg8(value.host_reg)); + } + break; + + case RegSize_16: + { + if (value.IsConstant()) + m_emit->mov(m_emit->word[m_emit->rip + ptr], value.constant_value); + else + m_emit->mov(m_emit->word[m_emit->rip + ptr], GetHostReg16(value.host_reg)); + } + break; + + case RegSize_32: + { + if (value.IsConstant()) + m_emit->mov(m_emit->dword[m_emit->rip + ptr], value.constant_value); + else + m_emit->mov(m_emit->dword[m_emit->rip + ptr], GetHostReg32(value.host_reg)); + } + break; + + case RegSize_64: + { + if (value.IsConstant()) + { + // we need a temporary to load the value if it doesn't fit in 32-bits + if (!Xbyak::inner::IsInInt32(value.constant_value)) + { + Value temp = m_register_cache.AllocateScratch(RegSize_64); + EmitCopyValue(temp.host_reg, value); + m_emit->mov(m_emit->qword[m_emit->rip + ptr], GetHostReg64(temp.host_reg)); + } + else + { + m_emit->mov(m_emit->qword[m_emit->rip + ptr], value.constant_value); + } + } + else + { + m_emit->mov(m_emit->qword[m_emit->rip + ptr], GetHostReg64(value.host_reg)); + } + } + break; + + default: + { + UnreachableCode(); + } + break; + } + } + else + { + Value address_temp = m_register_cache.AllocateScratch(RegSize_64); + m_emit->mov(GetHostReg64(address_temp), reinterpret_cast(ptr)); + switch (value.size) + { + case RegSize_8: + { + if (value.IsConstant()) + m_emit->mov(m_emit->byte[GetHostReg64(address_temp)], value.constant_value); + else + m_emit->mov(m_emit->byte[GetHostReg64(address_temp)], GetHostReg8(value.host_reg)); + } + break; + + case RegSize_16: + { + if (value.IsConstant()) + m_emit->mov(m_emit->word[GetHostReg64(address_temp)], value.constant_value); + else + m_emit->mov(m_emit->word[GetHostReg64(address_temp)], GetHostReg16(value.host_reg)); + } + break; + + case RegSize_32: + { + if (value.IsConstant()) + m_emit->mov(m_emit->dword[GetHostReg64(address_temp)], value.constant_value); + else + m_emit->mov(m_emit->dword[GetHostReg64(address_temp)], GetHostReg32(value.host_reg)); + } + break; + + case RegSize_64: + { + if (value.IsConstant()) + { + // we need a temporary to load the value if it doesn't fit in 32-bits + if (!Xbyak::inner::IsInInt32(value.constant_value)) + { + Value temp = m_register_cache.AllocateScratch(RegSize_64); + EmitCopyValue(temp.host_reg, value); + m_emit->mov(m_emit->qword[GetHostReg64(address_temp)], GetHostReg64(temp.host_reg)); + } + else + { + m_emit->mov(m_emit->qword[GetHostReg64(address_temp)], value.constant_value); + } + } + else + { + m_emit->mov(m_emit->qword[GetHostReg64(address_temp)], GetHostReg64(value.host_reg)); + } + } + break; + + default: + { + UnreachableCode(); + } + break; + } + } +} + void CodeGenerator::EmitFlushInterpreterLoadDelay() { Value reg = m_register_cache.AllocateScratch(RegSize_8); Value value = m_register_cache.AllocateScratch(RegSize_32); - auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(Core, m_load_delay_reg)]; - auto load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(Core, m_load_delay_value)]; - auto reg_ptr = m_emit->dword[GetCPUPtrReg() + offsetof(Core, m_regs.r[0]) + GetHostReg64(reg.host_reg) * 4]; + auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(State, load_delay_reg)]; + auto load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(State, load_delay_value)]; + auto reg_ptr = m_emit->dword[GetCPUPtrReg() + offsetof(State, regs.r[0]) + GetHostReg64(reg.host_reg) * 4]; Xbyak::Label skip_flush; @@ -1768,10 +1970,10 @@ void CodeGenerator::EmitMoveNextInterpreterLoadDelay() Value reg = m_register_cache.AllocateScratch(RegSize_8); Value value = m_register_cache.AllocateScratch(RegSize_32); - auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(Core, m_load_delay_reg)]; - auto load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(Core, m_load_delay_value)]; - auto next_load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(Core, m_next_load_delay_reg)]; - auto next_load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(Core, m_next_load_delay_value)]; + auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(State, load_delay_reg)]; + auto load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(State, load_delay_value)]; + auto next_load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(State, next_load_delay_reg)]; + auto next_load_delay_value = m_emit->dword[GetCPUPtrReg() + offsetof(State, next_load_delay_value)]; m_emit->mov(GetHostReg32(value), next_load_delay_value); m_emit->mov(GetHostReg8(reg), next_load_delay_reg); @@ -1785,7 +1987,7 @@ void CodeGenerator::EmitCancelInterpreterLoadDelayForReg(Reg reg) if (!m_load_delay_dirty) return; - auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(Core, m_load_delay_reg)]; + auto load_delay_reg = m_emit->byte[GetCPUPtrReg() + offsetof(State, load_delay_reg)]; Xbyak::Label skip_cancel; @@ -2047,6 +2249,4 @@ void CodeGenerator::EmitBindLabel(LabelType* label) m_emit->L(*label); } -void ASMFunctions::Generate(JitCodeBuffer* code_buffer) {} - } // namespace CPU::Recompiler diff --git a/src/core/cpu_recompiler_thunks.cpp b/src/core/cpu_recompiler_thunks.cpp deleted file mode 100644 index c1dcbc7f5..000000000 --- a/src/core/cpu_recompiler_thunks.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "cpu_recompiler_thunks.h" -#include "cpu_code_cache.h" -#include "cpu_core.h" - -namespace CPU::Recompiler { - -u32 Thunks::MakeRaiseExceptionInfo(Exception excode, const CodeBlockInstruction& cbi) -{ - RaiseExceptionInfo ri = {}; - ri.excode = static_cast(excode); - ri.BD = cbi.is_branch_delay_slot; - ri.CE = cbi.instruction.cop.cop_n; - return ri.bits; -} - -// TODO: Port thunks to "ASM routines", i.e. code in the jit buffer. - -u64 Thunks::ReadMemoryByte(Core* cpu, u32 pc, u32 address) -{ - cpu->m_current_instruction_pc = pc; - - u32 temp = 0; - const TickCount cycles = cpu->DoMemoryAccess(address, temp); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return UINT64_C(0xFFFFFFFFFFFFFFFF); - } - - cpu->m_pending_ticks += cycles; - return ZeroExtend64(temp); -} - -u64 Thunks::ReadMemoryHalfWord(Core* cpu, u32 pc, u32 address) -{ - cpu->m_current_instruction_pc = pc; - - if (!cpu->DoAlignmentCheck(address)) - return UINT64_C(0xFFFFFFFFFFFFFFFF); - - u32 temp = 0; - const TickCount cycles = cpu->DoMemoryAccess(address, temp); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return UINT64_C(0xFFFFFFFFFFFFFFFF); - } - - cpu->m_pending_ticks += cycles; - return ZeroExtend64(temp); -} - -u64 Thunks::ReadMemoryWord(Core* cpu, u32 pc, u32 address) -{ - cpu->m_current_instruction_pc = pc; - - if (!cpu->DoAlignmentCheck(address)) - return UINT64_C(0xFFFFFFFFFFFFFFFF); - - u32 temp = 0; - const TickCount cycles = cpu->DoMemoryAccess(address, temp); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return UINT64_C(0xFFFFFFFFFFFFFFFF); - } - - cpu->m_pending_ticks += cycles; - return ZeroExtend64(temp); -} - -bool Thunks::WriteMemoryByte(Core* cpu, u32 pc, u32 address, u8 value) -{ - cpu->m_current_instruction_pc = pc; - - u32 temp = ZeroExtend32(value); - const TickCount cycles = cpu->DoMemoryAccess(address, temp); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Thunks::WriteMemoryHalfWord(Core* cpu, u32 pc, u32 address, u16 value) -{ - cpu->m_current_instruction_pc = pc; - - if (!cpu->DoAlignmentCheck(address)) - return false; - - u32 temp = ZeroExtend32(value); - const TickCount cycles = cpu->DoMemoryAccess(address, temp); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Thunks::WriteMemoryWord(Core* cpu, u32 pc, u32 address, u32 value) -{ - cpu->m_current_instruction_pc = pc; - - if (!cpu->DoAlignmentCheck(address)) - return false; - - const TickCount cycles = cpu->DoMemoryAccess(address, value); - if (cycles < 0) - { - cpu->RaiseException(Exception::DBE); - return false; - } - - DebugAssert(cycles == 0); - return true; -} - -bool Thunks::InterpretInstruction(Core* cpu) -{ - cpu->ExecuteInstruction(); - return cpu->m_exception_raised; -} - -void Thunks::UpdateLoadDelay(Core* cpu) -{ - cpu->UpdateLoadDelay(); -} - -void Thunks::RaiseException(Core* cpu, u32 epc, u32 ri_bits) -{ - const RaiseExceptionInfo ri{ri_bits}; - cpu->RaiseException(static_cast(ri.excode), epc, ri.BD, cpu->m_current_instruction_was_branch_taken, - ri.CE); -} - -void Thunks::RaiseAddressException(Core* cpu, u32 address, bool store, bool branch) -{ - cpu->m_cop0_regs.BadVaddr = address; - if (branch) - cpu->RaiseException(Exception::AdEL, address, false, false, 0); - else - cpu->RaiseException(store ? Exception::AdES : Exception::AdEL); -} - -void Thunks::ExecuteGTEInstruction(Core* cpu, u32 instruction_bits) -{ - cpu->m_cop2.ExecuteInstruction(GTE::Instruction{instruction_bits}); -} - -u32 Thunks::ReadGTERegister(Core* cpu, u32 reg) -{ - return cpu->m_cop2.ReadRegister(reg); -} - -void Thunks::WriteGTERegister(Core* cpu, u32 reg, u32 value) -{ - cpu->m_cop2.WriteRegister(reg, value); -} - -} // namespace CPU::Recompiler \ No newline at end of file diff --git a/src/core/cpu_recompiler_thunks.h b/src/core/cpu_recompiler_thunks.h index 28c4db1d1..8aeaef620 100644 --- a/src/core/cpu_recompiler_thunks.h +++ b/src/core/cpu_recompiler_thunks.h @@ -1,67 +1,51 @@ #pragma once +#include "cpu_code_cache.h" #include "cpu_types.h" -class JitCodeBuffer; - namespace CPU { - struct CodeBlockInstruction; -class Core; +namespace Recompiler::Thunks { -namespace Recompiler { - -class Thunks +union RaiseExceptionInfo { -public: - union RaiseExceptionInfo + u32 bits; + + struct { - u32 bits; - - struct - { - u8 excode; - bool BD; - u8 CE; - u8 unused; - }; + u8 excode; + bool BD; + u8 CE; + u8 unused; }; - - static u32 MakeRaiseExceptionInfo(Exception excode, const CodeBlockInstruction& cbi); - - ////////////////////////////////////////////////////////////////////////// - // Trampolines for calling back from the JIT - // Needed because we can't cast member functions to void*... - // TODO: Abuse carry flag or something else for exception - ////////////////////////////////////////////////////////////////////////// - static u64 ReadMemoryByte(Core* cpu, u32 pc, u32 address); - static u64 ReadMemoryHalfWord(Core* cpu, u32 pc, u32 address); - static u64 ReadMemoryWord(Core* cpu, u32 pc, u32 address); - static bool WriteMemoryByte(Core* cpu, u32 pc, u32 address, u8 value); - static bool WriteMemoryHalfWord(Core* cpu, u32 pc, u32 address, u16 value); - static bool WriteMemoryWord(Core* cpu, u32 pc, u32 address, u32 value); - static bool InterpretInstruction(Core* cpu); - static void UpdateLoadDelay(Core* cpu); - static void RaiseException(Core* cpu, u32 epc, u32 ri_bits); - static void RaiseAddressException(Core* cpu, u32 address, bool store, bool branch); - static void ExecuteGTEInstruction(Core* cpu, u32 instruction_bits); - static u32 ReadGTERegister(Core* cpu, u32 reg); - static void WriteGTERegister(Core* cpu, u32 reg, u32 value); }; -class ASMFunctions +ALWAYS_INLINE u32 MakeRaiseExceptionInfo(Exception excode, const CodeBlockInstruction& cbi) { -public: - bool (*read_memory_byte)(u32 address, u8* value); - bool (*read_memory_word)(u32 address, u16* value); - bool (*read_memory_dword)(u32 address, u32* value); - void (*write_memory_byte)(u32 address, u8 value); - void (*write_memory_word)(u32 address, u16 value); - void (*write_memory_dword)(u32 address, u32 value); + RaiseExceptionInfo ri = {}; + ri.excode = static_cast(excode); + ri.BD = cbi.is_branch_delay_slot; + ri.CE = cbi.instruction.cop.cop_n; + return ri.bits; +} - void Generate(JitCodeBuffer* code_buffer); -}; +////////////////////////////////////////////////////////////////////////// +// Trampolines for calling back from the JIT +// Needed because we can't cast member functions to void*... +// TODO: Abuse carry flag or something else for exception +////////////////////////////////////////////////////////////////////////// +bool InterpretInstruction(); +void RaiseException(u32 epc, u32 ri_bits); +void RaiseAddressException(u32 address, bool store, bool branch); -} // namespace Recompiler +// Memory access functions for the JIT - MSB is set on exception. +u64 ReadMemoryByte(u32 pc, u32 address); +u64 ReadMemoryHalfWord(u32 pc, u32 address); +u64 ReadMemoryWord(u32 pc, u32 address); +bool WriteMemoryByte(u32 pc, u32 address, u8 value); +bool WriteMemoryHalfWord(u32 pc, u32 address, u16 value); +bool WriteMemoryWord(u32 pc, u32 address, u32 value); + +} // namespace Recompiler::Thunks } // namespace CPU \ No newline at end of file diff --git a/src/core/cpu_recompiler_types.h b/src/core/cpu_recompiler_types.h index 1beb9c944..5fefca46e 100644 --- a/src/core/cpu_recompiler_types.h +++ b/src/core/cpu_recompiler_types.h @@ -21,9 +21,6 @@ namespace CPU { -class Core; -class CodeCache; - namespace Recompiler { class CodeGenerator; @@ -77,6 +74,9 @@ constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; // Are shifts implicitly masked to 0..31? constexpr bool SHIFTS_ARE_IMPLICITLY_MASKED = true; +// Alignment of code stoarge. +constexpr u32 CODE_STORAGE_ALIGNMENT = 4096; + // ABI selection #if defined(WIN32) #define ABI_WIN64 1 @@ -105,6 +105,9 @@ constexpr u32 MAX_FAR_HOST_BYTES_PER_INSTRUCTION = 128; // Are shifts implicitly masked to 0..31? constexpr bool SHIFTS_ARE_IMPLICITLY_MASKED = true; +// Alignment of code stoarge. +constexpr u32 CODE_STORAGE_ALIGNMENT = 4096; + #else using HostReg = int; diff --git a/src/core/cpu_types.h b/src/core/cpu_types.h index b02535c97..d5a1ab10f 100644 --- a/src/core/cpu_types.h +++ b/src/core/cpu_types.h @@ -4,8 +4,6 @@ namespace CPU { -class Core; - // Memory address mask used for fetching as well as loadstores (removes cached/uncached/user/kernel bits). enum : PhysicalMemoryAddress { diff --git a/src/core/dma.cpp b/src/core/dma.cpp index d307c576d..f6157f4cf 100644 --- a/src/core/dma.cpp +++ b/src/core/dma.cpp @@ -4,6 +4,7 @@ #include "common/log.h" #include "common/state_wrapper.h" #include "common/string_util.h" +#include "cpu_core.h" #include "gpu.h" #include "interrupt_controller.h" #include "mdec.h" @@ -11,27 +12,27 @@ #include "system.h" Log_SetChannel(DMA); +DMA g_dma; + DMA::DMA() = default; DMA::~DMA() = default; -void DMA::Initialize(System* system, Bus* bus, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, - SPU* spu, MDEC* mdec) +void DMA::Initialize() { - m_system = system; - m_bus = bus; - m_interrupt_controller = interrupt_controller; - m_gpu = gpu; - m_cdrom = cdrom; - m_spu = spu; - m_mdec = mdec; - - m_max_slice_ticks = system->GetSettings().dma_max_slice_ticks; - m_halt_ticks = system->GetSettings().dma_halt_ticks; + m_max_slice_ticks = g_settings.dma_max_slice_ticks; + m_halt_ticks = g_settings.dma_halt_ticks; m_transfer_buffer.resize(32); - m_unhalt_event = system->CreateTimingEvent("DMA Transfer Unhalt", 1, m_max_slice_ticks, - std::bind(&DMA::UnhaltTransfer, this, std::placeholders::_1), false); + m_unhalt_event = TimingEvents::CreateTimingEvent("DMA Transfer Unhalt", 1, m_max_slice_ticks, + std::bind(&DMA::UnhaltTransfer, this, std::placeholders::_1), false); + + Reset(); +} + +void DMA::Shutdown() +{ + m_unhalt_event.reset(); } void DMA::Reset() @@ -238,7 +239,7 @@ void DMA::UpdateIRQ() if (m_DICR.master_flag) { Log_TracePrintf("Firing DMA master interrupt"); - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::DMA); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::DMA); } } @@ -267,7 +268,7 @@ bool DMA::TransferChannel(Channel channel) else used_ticks = TransferDeviceToMemory(channel, current_address & ADDRESS_MASK, increment, word_count); - m_system->StallCPU(used_ticks); + CPU::AddPendingTicks(used_ticks); } break; @@ -283,7 +284,7 @@ bool DMA::TransferChannel(Channel channel) Log_DebugPrintf("DMA%u: Copying linked list starting at 0x%08X to device", static_cast(channel), current_address & ADDRESS_MASK); - u8* ram_pointer = m_bus->GetRAM(); + u8* ram_pointer = Bus::g_ram; bool halt_transfer = false; while (cs.request) { @@ -319,7 +320,7 @@ bool DMA::TransferChannel(Channel channel) } cs.base_address = current_address; - m_system->StallCPU(used_ticks); + CPU::AddPendingTicks(used_ticks); if (current_address & UINT32_C(0x800000)) break; @@ -370,7 +371,7 @@ bool DMA::TransferChannel(Channel channel) cs.base_address = current_address & BASE_ADDRESS_MASK; cs.block_control.request.block_count = blocks_remaining; - m_system->StallCPU(used_ticks); + CPU::AddPendingTicks(used_ticks); // finish transfer later if the request was cleared if (blocks_remaining > 0) @@ -427,28 +428,34 @@ void DMA::UnhaltTransfer(TickCount ticks) TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count) { - const u32* src_pointer = reinterpret_cast(m_bus->GetRAM() + address); + const u32* src_pointer = reinterpret_cast(Bus::g_ram + address); if (static_cast(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address) { // Use temp buffer if it's wrapping around if (m_transfer_buffer.size() < word_count) m_transfer_buffer.resize(word_count); src_pointer = m_transfer_buffer.data(); - m_bus->ReadWords(address, m_transfer_buffer.data(), word_count); + + u8* ram_pointer = Bus::g_ram; + for (u32 i = 0; i < word_count; i++) + { + std::memcpy(&m_transfer_buffer[i], &ram_pointer[address], sizeof(u32)); + address = (address + increment) & ADDRESS_MASK; + } } switch (channel) { case Channel::GPU: - m_gpu->DMAWrite(src_pointer, word_count); + g_gpu->DMAWrite(src_pointer, word_count); break; case Channel::SPU: - m_spu->DMAWrite(src_pointer, word_count); + g_spu.DMAWrite(src_pointer, word_count); break; case Channel::MDECin: - m_mdec->DMAWrite(src_pointer, word_count); + g_mdec.DMAWrite(src_pointer, word_count); break; case Channel::CDROM: @@ -459,7 +466,7 @@ TickCount DMA::TransferMemoryToDevice(Channel channel, u32 address, u32 incremen break; } - return m_bus->GetDMARAMTickCount(word_count); + return Bus::GetDMARAMTickCount(word_count); } TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 increment, u32 word_count) @@ -467,7 +474,7 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen if (channel == Channel::OTC) { // clear ordering table - u8* ram_pointer = m_bus->GetRAM(); + u8* ram_pointer = Bus::g_ram; const u32 word_count_less_1 = word_count - 1; for (u32 i = 0; i < word_count_less_1; i++) { @@ -478,11 +485,11 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen const u32 terminator = UINT32_C(0xFFFFFF); std::memcpy(&ram_pointer[address], &terminator, sizeof(terminator)); - m_bus->InvalidateCodePages(address, word_count); - return m_bus->GetDMARAMTickCount(word_count); + Bus::InvalidateCodePages(address, word_count); + return Bus::GetDMARAMTickCount(word_count); } - u32* dest_pointer = reinterpret_cast(&m_bus->m_ram[address]); + u32* dest_pointer = reinterpret_cast(&Bus::g_ram[address]); if (static_cast(increment) < 0 || ((address + (increment * word_count)) & ADDRESS_MASK) <= address) { // Use temp buffer if it's wrapping around @@ -495,19 +502,19 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen switch (channel) { case Channel::GPU: - m_gpu->DMARead(dest_pointer, word_count); + g_gpu->DMARead(dest_pointer, word_count); break; case Channel::CDROM: - m_cdrom->DMARead(dest_pointer, word_count); + g_cdrom.DMARead(dest_pointer, word_count); break; case Channel::SPU: - m_spu->DMARead(dest_pointer, word_count); + g_spu.DMARead(dest_pointer, word_count); break; case Channel::MDECout: - m_mdec->DMARead(dest_pointer, word_count); + g_mdec.DMARead(dest_pointer, word_count); break; default: @@ -518,7 +525,7 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen if (dest_pointer == m_transfer_buffer.data()) { - u8* ram_pointer = m_bus->m_ram; + u8* ram_pointer = Bus::g_ram; for (u32 i = 0; i < word_count; i++) { std::memcpy(&ram_pointer[address], &m_transfer_buffer[i], sizeof(u32)); @@ -526,6 +533,6 @@ TickCount DMA::TransferDeviceToMemory(Channel channel, u32 address, u32 incremen } } - m_bus->InvalidateCodePages(address, word_count); - return m_bus->GetDMARAMTickCount(word_count); + Bus::InvalidateCodePages(address, word_count); + return Bus::GetDMARAMTickCount(word_count); } diff --git a/src/core/dma.h b/src/core/dma.h index 2f6558611..d7d9ca5aa 100644 --- a/src/core/dma.h +++ b/src/core/dma.h @@ -7,14 +7,7 @@ class StateWrapper; -class System; class TimingEvent; -class Bus; -class InterruptController; -class GPU; -class CDROM; -class SPU; -class MDEC; class DMA { @@ -38,8 +31,8 @@ public: DMA(); ~DMA(); - void Initialize(System* system, Bus* bus, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, SPU* spu, - MDEC* mdec); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -49,7 +42,6 @@ public: void SetRequest(Channel channel, bool request); // changing interfaces - void SetGPU(GPU* gpu) { m_gpu = gpu; } void SetMaxSliceTicks(TickCount ticks) { m_max_slice_ticks = ticks; } void SetHaltTicks(TickCount ticks) { m_halt_ticks = ticks; } @@ -82,14 +74,6 @@ private: // from memory -> device TickCount TransferMemoryToDevice(Channel channel, u32 address, u32 increment, u32 word_count); - System* m_system = nullptr; - Bus* m_bus = nullptr; - InterruptController* m_interrupt_controller = nullptr; - GPU* m_gpu = nullptr; - CDROM* m_cdrom = nullptr; - SPU* m_spu = nullptr; - MDEC* m_mdec = nullptr; - // configuration TickCount m_max_slice_ticks = 1000; TickCount m_halt_ticks = 100; @@ -212,3 +196,5 @@ private: } } m_DICR = {}; }; + +extern DMA g_dma; \ No newline at end of file diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 8bd307690..deabe216f 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -13,51 +13,45 @@ #include Log_SetChannel(GPU); +std::unique_ptr g_gpu; + const GPU::GP0CommandHandlerTable GPU::s_GP0_command_handler_table = GPU::GenerateGP0CommandHandlerTable(); GPU::GPU() = default; GPU::~GPU() = default; -bool GPU::Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) +bool GPU::Initialize(HostDisplay* host_display) { m_host_display = host_display; - m_system = system; - m_dma = dma; - m_interrupt_controller = interrupt_controller; - m_timers = timers; - m_force_progressive_scan = m_system->GetSettings().gpu_disable_interlacing; - m_force_ntsc_timings = m_system->GetSettings().gpu_force_ntsc_timings; - m_crtc_state.display_aspect_ratio = - Settings::GetDisplayAspectRatioValue(m_system->GetSettings().display_aspect_ratio); - m_crtc_tick_event = m_system->CreateTimingEvent("GPU CRTC Tick", 1, 1, - std::bind(&GPU::CRTCTickEvent, this, std::placeholders::_1), true); - m_command_tick_event = m_system->CreateTimingEvent( + m_force_progressive_scan = g_settings.gpu_disable_interlacing; + m_force_ntsc_timings = g_settings.gpu_force_ntsc_timings; + m_crtc_state.display_aspect_ratio = Settings::GetDisplayAspectRatioValue(g_settings.display_aspect_ratio); + m_crtc_tick_event = TimingEvents::CreateTimingEvent( + "GPU CRTC Tick", 1, 1, std::bind(&GPU::CRTCTickEvent, this, std::placeholders::_1), true); + m_command_tick_event = TimingEvents::CreateTimingEvent( "GPU Command Tick", 1, 1, std::bind(&GPU::CommandTickEvent, this, std::placeholders::_1), true); - m_fifo_size = system->GetSettings().gpu_fifo_size; - m_max_run_ahead = system->GetSettings().gpu_max_run_ahead; - m_console_is_pal = system->IsPALRegion(); + m_fifo_size = g_settings.gpu_fifo_size; + m_max_run_ahead = g_settings.gpu_max_run_ahead; + m_console_is_pal = System::IsPALRegion(); UpdateCRTCConfig(); return true; } void GPU::UpdateSettings() { - const Settings& settings = m_system->GetSettings(); + m_force_progressive_scan = g_settings.gpu_disable_interlacing; + m_fifo_size = g_settings.gpu_fifo_size; + m_max_run_ahead = g_settings.gpu_max_run_ahead; - m_force_progressive_scan = settings.gpu_disable_interlacing; - m_fifo_size = settings.gpu_fifo_size; - m_max_run_ahead = settings.gpu_max_run_ahead; - - if (m_force_ntsc_timings != settings.gpu_force_ntsc_timings || m_console_is_pal != m_system->IsPALRegion()) + if (m_force_ntsc_timings != g_settings.gpu_force_ntsc_timings || m_console_is_pal != System::IsPALRegion()) { - m_force_ntsc_timings = settings.gpu_force_ntsc_timings; - m_console_is_pal = m_system->IsPALRegion(); + m_force_ntsc_timings = g_settings.gpu_force_ntsc_timings; + m_console_is_pal = System::IsPALRegion(); UpdateCRTCConfig(); } - m_crtc_state.display_aspect_ratio = Settings::GetDisplayAspectRatioValue(settings.display_aspect_ratio); + m_crtc_state.display_aspect_ratio = Settings::GetDisplayAspectRatioValue(g_settings.display_aspect_ratio); // Crop mode calls this, so recalculate the display area UpdateCRTCDisplayParameters(); @@ -75,7 +69,7 @@ void GPU::SoftReset() FlushRender(); m_GPUSTAT.bits = 0x14802000; - m_GPUSTAT.pal_mode = m_system->IsPALRegion(); + m_GPUSTAT.pal_mode = System::IsPALRegion(); m_drawing_area.Set(0, 0, 0, 0); m_drawing_area_changed = true; m_drawing_offset = {}; @@ -276,7 +270,7 @@ void GPU::UpdateDMARequest() break; } m_GPUSTAT.dma_data_request = dma_request; - m_dma->SetRequest(DMA::Channel::GPU, dma_request); + g_dma.SetRequest(DMA::Channel::GPU, dma_request); } void GPU::UpdateGPUIdle() @@ -498,7 +492,7 @@ void GPU::UpdateCRTCConfig() cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; } - m_system->SetThrottleFrequency(ComputeVerticalFrequency()); + System::SetThrottleFrequency(ComputeVerticalFrequency()); UpdateCRTCDisplayParameters(); UpdateCRTCTickEvent(); @@ -507,7 +501,7 @@ void GPU::UpdateCRTCConfig() void GPU::UpdateCRTCDisplayParameters() { CRTCState& cs = m_crtc_state; - const DisplayCropMode crop_mode = m_system->GetSettings().display_crop_mode; + const DisplayCropMode crop_mode = g_settings.display_crop_mode; const u16 horizontal_total = m_GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; const u16 vertical_total = m_GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; @@ -657,8 +651,8 @@ void GPU::UpdateCRTCTickEvent() (m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end ? (m_crtc_state.vertical_total - m_crtc_state.current_scanline + m_crtc_state.vertical_display_end) : (m_crtc_state.vertical_display_end - m_crtc_state.current_scanline)); - const TickCount lines_until_event = m_timers->IsExternalIRQEnabled(HBLANK_TIMER_INDEX) ? - std::min(m_timers->GetTicksUntilIRQ(HBLANK_TIMER_INDEX), lines_until_vblank) : + const TickCount lines_until_event = g_timers.IsExternalIRQEnabled(HBLANK_TIMER_INDEX) ? + std::min(g_timers.GetTicksUntilIRQ(HBLANK_TIMER_INDEX), lines_until_vblank) : lines_until_vblank; const TickCount ticks_until_event = lines_until_event * m_crtc_state.horizontal_total - m_crtc_state.current_tick_in_scanline; @@ -697,8 +691,8 @@ void GPU::CRTCTickEvent(TickCount ticks) const bool old_hblank = m_crtc_state.in_hblank; const bool new_hblank = m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start || m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end; - if (!old_hblank && new_hblank && m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX)) - m_timers->AddTicks(HBLANK_TIMER_INDEX, 1); + if (!old_hblank && new_hblank && g_timers.IsUsingExternalClock(HBLANK_TIMER_INDEX)) + g_timers.AddTicks(HBLANK_TIMER_INDEX, 1); UpdateCRTCTickEvent(); return; @@ -715,10 +709,10 @@ void GPU::CRTCTickEvent(TickCount ticks) const bool new_hblank = m_crtc_state.current_tick_in_scanline < m_crtc_state.horizontal_display_start || m_crtc_state.current_tick_in_scanline >= m_crtc_state.horizontal_display_end; m_crtc_state.in_hblank = new_hblank; - if (m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX)) + if (g_timers.IsUsingExternalClock(HBLANK_TIMER_INDEX)) { const u32 hblank_timer_ticks = BoolToUInt32(!old_hblank) + BoolToUInt32(new_hblank) + (lines_to_draw - 1); - m_timers->AddTicks(HBLANK_TIMER_INDEX, static_cast(hblank_timer_ticks)); + g_timers.AddTicks(HBLANK_TIMER_INDEX, static_cast(hblank_timer_ticks)); } while (lines_to_draw > 0) @@ -734,7 +728,7 @@ void GPU::CRTCTickEvent(TickCount ticks) if (prev_scanline < m_crtc_state.vertical_display_start && m_crtc_state.current_scanline >= m_crtc_state.vertical_display_end) { - m_timers->SetGate(HBLANK_TIMER_INDEX, false); + g_timers.SetGate(HBLANK_TIMER_INDEX, false); m_crtc_state.in_vblank = false; } @@ -745,12 +739,12 @@ void GPU::CRTCTickEvent(TickCount ticks) if (new_vblank) { Log_DebugPrintf("Now in v-blank"); - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::VBLANK); // flush any pending draws and "scan out" the image FlushRender(); UpdateDisplay(); - m_system->IncrementFrameNumber(); + System::FrameDone(); // switch fields early. this is needed so we draw to the correct one. if (m_GPUSTAT.vertical_interlace) @@ -759,7 +753,7 @@ void GPU::CRTCTickEvent(TickCount ticks) m_crtc_state.interlaced_field = 0; } - m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank); + g_timers.SetGate(HBLANK_TIMER_INDEX, new_vblank); m_crtc_state.in_vblank = new_vblank; } @@ -938,7 +932,7 @@ void GPU::WriteGP1(u32 value) { m_crtc_state.regs.display_address_start = param & CRTCState::Regs::DISPLAY_ADDRESS_START_MASK; Log_DebugPrintf("Display address start <- 0x%08X", m_crtc_state.regs.display_address_start); - m_system->IncrementInternalFrameNumber(); + System::IncrementInternalFrameNumber(); UpdateCRTCDisplayParameters(); } break; @@ -1339,7 +1333,7 @@ void GPU::DrawDebugStateWindow() const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(450.0f * framebuffer_scale, 550.0f * framebuffer_scale), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("GPU", &m_system->GetSettings().debugging.show_gpu_state)) + if (!ImGui::Begin("GPU", &g_settings.debugging.show_gpu_state)) { ImGui::End(); return; diff --git a/src/core/gpu.h b/src/core/gpu.h index acd434091..668adde1b 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -15,10 +15,7 @@ class StateWrapper; class HostDisplay; -class System; class TimingEvent; -class DMA; -class InterruptController; class Timers; class GPU @@ -122,8 +119,7 @@ public: virtual bool IsHardwareRenderer() const = 0; - virtual bool Initialize(HostDisplay* host_display, System* system, DMA* dma, - InterruptController* interrupt_controller, Timers* timers); + virtual bool Initialize(HostDisplay* host_display); virtual void Reset(); virtual bool DoState(StateWrapper& sw); @@ -452,10 +448,6 @@ protected: } HostDisplay* m_host_display = nullptr; - System* m_system = nullptr; - DMA* m_dma = nullptr; - InterruptController* m_interrupt_controller = nullptr; - Timers* m_timers = nullptr; std::unique_ptr m_crtc_tick_event; std::unique_ptr m_command_tick_event; @@ -757,3 +749,5 @@ private: }; IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(GPU::TextureMode); + +extern std::unique_ptr g_gpu; diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp index e7d826ec4..6d658ac18 100644 --- a/src/core/gpu_commands.cpp +++ b/src/core/gpu_commands.cpp @@ -205,7 +205,7 @@ bool GPU::HandleInterruptRequestCommand() if (!m_GPUSTAT.interrupt_request) { m_GPUSTAT.interrupt_request = true; - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::GPU); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::GPU); } m_fifo.RemoveOne(); @@ -496,7 +496,7 @@ bool GPU::HandleCopyRectangleCPUToVRAMCommand() void GPU::FinishVRAMWrite() { - if (m_system->GetSettings().debugging.dump_cpu_to_vram_copies) + if (g_settings.debugging.dump_cpu_to_vram_copies) { DumpVRAMToFile(StringUtil::StdStringFromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++).c_str(), m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width, @@ -535,7 +535,7 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand() // ensure VRAM shadow is up to date ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height); - if (m_system->GetSettings().debugging.dump_vram_to_cpu_copies) + if (g_settings.debugging.dump_vram_to_cpu_copies) { DumpVRAMToFile(StringUtil::StdStringFromFormat("vram_to_cpu_copy_%u.png", s_vram_to_cpu_dump_id++).c_str(), m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * VRAM_WIDTH, diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index c4a365992..aaf93604f 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -8,34 +8,26 @@ #include Log_SetChannel(GPU_HW); -GPU_HW::GPU_HW() : GPU() -{ - m_vram_ptr = m_vram_shadow.data(); -} +GPU_HW::GPU_HW() : GPU() { m_vram_ptr = m_vram_shadow.data(); } GPU_HW::~GPU_HW() = default; -bool GPU_HW::IsHardwareRenderer() const -{ - return true; -} +bool GPU_HW::IsHardwareRenderer() const { return true; } -bool GPU_HW::Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) +bool GPU_HW::Initialize(HostDisplay* host_display) { - if (!GPU::Initialize(host_display, system, dma, interrupt_controller, timers)) + if (!GPU::Initialize(host_display)) return false; - const Settings& settings = m_system->GetSettings(); - m_resolution_scale = settings.gpu_resolution_scale; + m_resolution_scale = g_settings.gpu_resolution_scale; m_render_api = host_display->GetRenderAPI(); - m_true_color = settings.gpu_true_color; - m_scaled_dithering = settings.gpu_scaled_dithering; - m_texture_filtering = settings.gpu_texture_filtering; + m_true_color = g_settings.gpu_true_color; + m_scaled_dithering = g_settings.gpu_scaled_dithering; + m_texture_filtering = g_settings.gpu_texture_filtering; if (m_resolution_scale < 1 || m_resolution_scale > m_max_resolution_scale) { - m_system->GetHostInterface()->AddFormattedOSDMessage(5.0f, "Invalid resolution scale %ux specified. Maximum is %u.", - m_resolution_scale, m_max_resolution_scale); + g_host_interface->AddFormattedOSDMessage(5.0f, "Invalid resolution scale %ux specified. Maximum is %u.", + m_resolution_scale, m_max_resolution_scale); m_resolution_scale = std::clamp(m_resolution_scale, 1u, m_max_resolution_scale); } @@ -79,11 +71,10 @@ void GPU_HW::UpdateSettings() { GPU::UpdateSettings(); - const Settings& settings = m_system->GetSettings(); - m_resolution_scale = std::clamp(settings.gpu_resolution_scale, 1, m_max_resolution_scale); - m_true_color = settings.gpu_true_color; - m_scaled_dithering = settings.gpu_scaled_dithering; - m_texture_filtering = settings.gpu_texture_filtering; + m_resolution_scale = std::clamp(g_settings.gpu_resolution_scale, 1, m_max_resolution_scale); + m_true_color = g_settings.gpu_true_color; + m_scaled_dithering = g_settings.gpu_scaled_dithering; + m_texture_filtering = g_settings.gpu_texture_filtering; PrintSettingsToLog(); } diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 90ec7620a..ae978763e 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -37,8 +37,7 @@ public: virtual bool IsHardwareRenderer() const override; - virtual bool Initialize(HostDisplay* host_display, System* system, DMA* dma, - InterruptController* interrupt_controller, Timers* timers) override; + virtual bool Initialize(HostDisplay* host_display) override; virtual void Reset() override; virtual bool DoState(StateWrapper& sw) override; virtual void UpdateSettings() override; diff --git a/src/core/gpu_hw_d3d11.cpp b/src/core/gpu_hw_d3d11.cpp index 0c56edf1a..d0bb17e02 100644 --- a/src/core/gpu_hw_d3d11.cpp +++ b/src/core/gpu_hw_d3d11.cpp @@ -19,8 +19,7 @@ GPU_HW_D3D11::~GPU_HW_D3D11() } } -bool GPU_HW_D3D11::Initialize(HostDisplay* host_display, System* system, DMA* dma, - InterruptController* interrupt_controller, Timers* timers) +bool GPU_HW_D3D11::Initialize(HostDisplay* host_display) { if (host_display->GetRenderAPI() != HostDisplay::RenderAPI::D3D11) { @@ -30,7 +29,7 @@ bool GPU_HW_D3D11::Initialize(HostDisplay* host_display, System* system, DMA* dm SetCapabilities(); - if (!GPU_HW::Initialize(host_display, system, dma, interrupt_controller, timers)) + if (!GPU_HW::Initialize(host_display)) return false; m_device = static_cast(host_display->GetRenderDevice()); @@ -38,8 +37,8 @@ bool GPU_HW_D3D11::Initialize(HostDisplay* host_display, System* system, DMA* dm if (!m_device || !m_context) return false; - m_shader_cache.Open(system->GetHostInterface()->GetShaderCacheBasePath(), m_device->GetFeatureLevel(), - system->GetSettings().gpu_use_debug_device); + m_shader_cache.Open(g_host_interface->GetShaderCacheBasePath(), m_device->GetFeatureLevel(), + g_settings.gpu_use_debug_device); if (!CreateFramebuffer()) { @@ -370,7 +369,7 @@ bool GPU_HW_D3D11::CompileShaders() GPU_HW_ShaderGen shadergen(m_host_display->GetRenderAPI(), m_resolution_scale, m_true_color, m_scaled_dithering, m_texture_filtering, m_supports_dual_source_blend); - m_system->GetHostInterface()->DisplayLoadingScreen("Compiling shaders..."); + g_host_interface->DisplayLoadingScreen("Compiling shaders..."); m_screen_quad_vertex_shader = m_shader_cache.GetVertexShader(m_device.Get(), shadergen.GenerateScreenQuadVertexShader()); @@ -572,7 +571,7 @@ void GPU_HW_D3D11::UpdateDisplay() { GPU_HW::UpdateDisplay(); - if (m_system->GetSettings().debugging.show_vram) + if (g_settings.debugging.show_vram) { m_host_display->SetDisplayTexture(m_vram_texture.GetD3DSRV(), m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight()); @@ -789,7 +788,4 @@ void GPU_HW_D3D11::UpdateDepthBufferFromMaskBit() RestoreGraphicsAPIState(); } -std::unique_ptr GPU::CreateHardwareD3D11Renderer() -{ - return std::make_unique(); -} +std::unique_ptr GPU::CreateHardwareD3D11Renderer() { return std::make_unique(); } diff --git a/src/core/gpu_hw_d3d11.h b/src/core/gpu_hw_d3d11.h index 4257a9b5d..b294d4709 100644 --- a/src/core/gpu_hw_d3d11.h +++ b/src/core/gpu_hw_d3d11.h @@ -19,8 +19,7 @@ public: GPU_HW_D3D11(); ~GPU_HW_D3D11() override; - bool Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) override; + bool Initialize(HostDisplay* host_display) override; void Reset() override; void ResetGraphicsAPIState() override; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index 07791b173..393a34158 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -27,8 +27,7 @@ GPU_HW_OpenGL::~GPU_HW_OpenGL() } } -bool GPU_HW_OpenGL::Initialize(HostDisplay* host_display, System* system, DMA* dma, - InterruptController* interrupt_controller, Timers* timers) +bool GPU_HW_OpenGL::Initialize(HostDisplay* host_display) { if (host_display->GetRenderAPI() != HostDisplay::RenderAPI::OpenGL && host_display->GetRenderAPI() != HostDisplay::RenderAPI::OpenGLES) @@ -39,9 +38,9 @@ bool GPU_HW_OpenGL::Initialize(HostDisplay* host_display, System* system, DMA* d SetCapabilities(host_display); - m_shader_cache.Open(IsGLES(), system->GetHostInterface()->GetShaderCacheBasePath()); + m_shader_cache.Open(IsGLES(), g_host_interface->GetShaderCacheBasePath()); - if (!GPU_HW::Initialize(host_display, system, dma, interrupt_controller, timers)) + if (!GPU_HW::Initialize(host_display)) return false; if (!CreateFramebuffer()) @@ -337,7 +336,7 @@ bool GPU_HW_OpenGL::CompilePrograms() GPU_HW_ShaderGen shadergen(m_host_display->GetRenderAPI(), m_resolution_scale, m_true_color, m_scaled_dithering, m_texture_filtering, m_supports_dual_source_blend); - m_system->GetHostInterface()->DisplayLoadingScreen("Compiling Shaders..."); + g_host_interface->DisplayLoadingScreen("Compiling Shaders..."); for (u32 render_mode = 0; render_mode < 4; render_mode++) { @@ -580,7 +579,7 @@ void GPU_HW_OpenGL::UpdateDisplay() { GPU_HW::UpdateDisplay(); - if (m_system->GetSettings().debugging.show_vram) + if (g_settings.debugging.show_vram) { m_host_display->SetDisplayTexture(reinterpret_cast(static_cast(m_vram_texture.GetGLId())), m_vram_texture.GetWidth(), static_cast(m_vram_texture.GetHeight()), 0, diff --git a/src/core/gpu_hw_opengl.h b/src/core/gpu_hw_opengl.h index 5e6dc9ee9..242fa046a 100644 --- a/src/core/gpu_hw_opengl.h +++ b/src/core/gpu_hw_opengl.h @@ -15,8 +15,7 @@ public: GPU_HW_OpenGL(); ~GPU_HW_OpenGL() override; - bool Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) override; + bool Initialize(HostDisplay* host_display) override; void Reset() override; void ResetGraphicsAPIState() override; diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index 8bdc81128..6eceb0b1c 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -25,8 +25,7 @@ GPU_HW_Vulkan::~GPU_HW_Vulkan() DestroyResources(); } -bool GPU_HW_Vulkan::Initialize(HostDisplay* host_display, System* system, DMA* dma, - InterruptController* interrupt_controller, Timers* timers) +bool GPU_HW_Vulkan::Initialize(HostDisplay* host_display) { if (host_display->GetRenderAPI() != HostDisplay::RenderAPI::Vulkan) { @@ -37,7 +36,7 @@ bool GPU_HW_Vulkan::Initialize(HostDisplay* host_display, System* system, DMA* d Assert(g_vulkan_shader_cache); SetCapabilities(); - if (!GPU_HW::Initialize(host_display, system, dma, interrupt_controller, timers)) + if (!GPU_HW::Initialize(host_display)) return false; if (!CreatePipelineLayouts()) @@ -570,7 +569,7 @@ bool GPU_HW_Vulkan::CompilePipelines() {VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST}}; static constexpr std::array polygon_mode_mapping = {{VK_POLYGON_MODE_LINE, VK_POLYGON_MODE_FILL}}; - m_system->GetHostInterface()->DisplayLoadingScreen("Compiling Shaders..."); + g_host_interface->DisplayLoadingScreen("Compiling Shaders..."); VkDevice device = g_vulkan_context->GetDevice(); VkPipelineCache pipeline_cache = g_vulkan_shader_cache->GetPipelineCache(); @@ -941,7 +940,7 @@ void GPU_HW_Vulkan::UpdateDisplay() { GPU_HW::UpdateDisplay(); - if (m_system->GetSettings().debugging.show_vram) + if (g_settings.debugging.show_vram) { m_host_display->SetDisplayTexture(&m_vram_texture, m_vram_texture.GetWidth(), m_vram_texture.GetHeight(), 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight()); diff --git a/src/core/gpu_hw_vulkan.h b/src/core/gpu_hw_vulkan.h index b5a79815a..b7cf8f940 100644 --- a/src/core/gpu_hw_vulkan.h +++ b/src/core/gpu_hw_vulkan.h @@ -14,8 +14,7 @@ public: GPU_HW_Vulkan(); ~GPU_HW_Vulkan() override; - bool Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) override; + bool Initialize(HostDisplay* host_display) override; void Reset() override; void ResetGraphicsAPIState() override; diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index c058ece3e..545270750 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -23,10 +23,9 @@ bool GPU_SW::IsHardwareRenderer() const return false; } -bool GPU_SW::Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) +bool GPU_SW::Initialize(HostDisplay* host_display) { - if (!GPU::Initialize(host_display, system, dma, interrupt_controller, timers)) + if (!GPU::Initialize(host_display)) return false; m_display_texture = host_display->CreateTexture(VRAM_WIDTH, VRAM_HEIGHT, nullptr, 0, true); @@ -148,7 +147,7 @@ void GPU_SW::UpdateDisplay() // fill display texture m_display_texture_buffer.resize(VRAM_WIDTH * VRAM_HEIGHT); - if (!m_system->GetSettings().debugging.show_vram) + if (!g_settings.debugging.show_vram) { if (IsDisplayDisabled()) { diff --git a/src/core/gpu_sw.h b/src/core/gpu_sw.h index 6381c2a11..f6940aa74 100644 --- a/src/core/gpu_sw.h +++ b/src/core/gpu_sw.h @@ -14,8 +14,7 @@ public: bool IsHardwareRenderer() const override; - bool Initialize(HostDisplay* host_display, System* system, DMA* dma, InterruptController* interrupt_controller, - Timers* timers) override; + bool Initialize(HostDisplay* host_display) override; void Reset() override; u16 GetPixel(u32 x, u32 y) const { return m_vram[VRAM_WIDTH * y + x]; } diff --git a/src/core/gte.cpp b/src/core/gte.cpp index e7fa80c7c..1b2a0d2d5 100644 --- a/src/core/gte.cpp +++ b/src/core/gte.cpp @@ -1,9 +1,26 @@ #include "gte.h" +#include "common/assert.h" #include "common/bitutils.h" +#include "common/state_wrapper.h" +#include "cpu_core.h" +#include "settings.h" #include #include -ALWAYS_INLINE u32 CountLeadingBits(u32 value) +namespace GTE { + +static constexpr s64 MAC0_MIN_VALUE = -(INT64_C(1) << 31); +static constexpr s64 MAC0_MAX_VALUE = (INT64_C(1) << 31) - 1; +static constexpr s64 MAC123_MIN_VALUE = -(INT64_C(1) << 43); +static constexpr s64 MAC123_MAX_VALUE = (INT64_C(1) << 43) - 1; +static constexpr s32 IR0_MIN_VALUE = 0x0000; +static constexpr s32 IR0_MAX_VALUE = 0x1000; +static constexpr s32 IR123_MIN_VALUE = -(INT64_C(1) << 15); +static constexpr s32 IR123_MAX_VALUE = (INT64_C(1) << 15) - 1; + +#define REGS CPU::g_state.gte_regs + +ALWAYS_INLINE static u32 CountLeadingBits(u32 value) { // if top-most bit is set, we want to count ones not zeros if (value & UINT32_C(0x80000000)) @@ -12,53 +29,166 @@ ALWAYS_INLINE u32 CountLeadingBits(u32 value) return (value == 0u) ? 32 : CountLeadingZeros(value); } -namespace GTE { - -Core::Core() = default; - -Core::~Core() = default; - -void Core::Initialize() {} - -void Core::Reset() +template +static void CheckMACOverflow(s64 value) { - std::memset(&m_regs, 0, sizeof(m_regs)); + constexpr s64 MIN_VALUE = (index == 0) ? MAC0_MIN_VALUE : MAC123_MIN_VALUE; + constexpr s64 MAX_VALUE = (index == 0) ? MAC0_MAX_VALUE : MAC123_MAX_VALUE; + if (value < MIN_VALUE) + { + if constexpr (index == 0) + REGS.FLAG.mac0_underflow = true; + else if constexpr (index == 1) + REGS.FLAG.mac1_underflow = true; + else if constexpr (index == 2) + REGS.FLAG.mac2_underflow = true; + else if constexpr (index == 3) + REGS.FLAG.mac3_underflow = true; + } + else if (value > MAX_VALUE) + { + if constexpr (index == 0) + REGS.FLAG.mac0_overflow = true; + else if constexpr (index == 1) + REGS.FLAG.mac1_overflow = true; + else if constexpr (index == 2) + REGS.FLAG.mac2_overflow = true; + else if constexpr (index == 3) + REGS.FLAG.mac3_overflow = true; + } } -bool Core::DoState(StateWrapper& sw) +template +static s64 SignExtendMACResult(s64 value) { - sw.DoArray(m_regs.r32, NUM_DATA_REGS + NUM_CONTROL_REGS); + CheckMACOverflow(value); + return SignExtendN < index == 0 ? 31 : 44 > (value); +} + +template +static void TruncateAndSetMAC(s64 value, u8 shift) +{ + CheckMACOverflow(value); + + // shift should be done before storing to avoid losing precision + value >>= shift; + + REGS.dr32[24 + index] = Truncate32(static_cast(value)); +} + +template +static void TruncateAndSetIR(s32 value, bool lm) +{ + constexpr s32 MIN_VALUE = (index == 0) ? IR0_MIN_VALUE : IR123_MIN_VALUE; + constexpr s32 MAX_VALUE = (index == 0) ? IR0_MAX_VALUE : IR123_MAX_VALUE; + const s32 actual_min_value = lm ? 0 : MIN_VALUE; + if (value < actual_min_value) + { + value = actual_min_value; + if constexpr (index == 0) + REGS.FLAG.ir0_saturated = true; + else if constexpr (index == 1) + REGS.FLAG.ir1_saturated = true; + else if constexpr (index == 2) + REGS.FLAG.ir2_saturated = true; + else if constexpr (index == 3) + REGS.FLAG.ir3_saturated = true; + } + else if (value > MAX_VALUE) + { + value = MAX_VALUE; + if constexpr (index == 0) + REGS.FLAG.ir0_saturated = true; + else if constexpr (index == 1) + REGS.FLAG.ir1_saturated = true; + else if constexpr (index == 2) + REGS.FLAG.ir2_saturated = true; + else if constexpr (index == 3) + REGS.FLAG.ir3_saturated = true; + } + + // store sign-extended 16-bit value as 32-bit + REGS.dr32[8 + index] = value; +} + +template +static void TruncateAndSetMACAndIR(s64 value, u8 shift, bool lm) +{ + CheckMACOverflow(value); + + // shift should be done before storing to avoid losing precision + value >>= shift; + + // set MAC + const s32 value32 = static_cast(value); + REGS.dr32[24 + index] = value32; + + // set IR + TruncateAndSetIR(value32, lm); +} + +template +static u32 TruncateRGB(s32 value) +{ + if (value < 0 || value > 0xFF) + { + if constexpr (index == 0) + REGS.FLAG.color_r_saturated = true; + else if constexpr (index == 1) + REGS.FLAG.color_g_saturated = true; + else + REGS.FLAG.color_b_saturated = true; + + return (value < 0) ? 0 : 0xFF; + } + + return static_cast(value); +} + +void Initialize() +{ + Reset(); +} + +void Reset() +{ + std::memset(®S, 0, sizeof(REGS)); +} + +bool DoState(StateWrapper& sw) +{ + sw.DoArray(REGS.r32, NUM_DATA_REGS + NUM_CONTROL_REGS); return !sw.HasError(); } -u32 Core::ReadRegister(u32 index) const +u32 ReadRegister(u32 index) { - DebugAssert(index < countof(m_regs.r32)); + DebugAssert(index < countof(REGS.r32)); switch (index) { case 15: // SXY3 { // mirror of SXY2 - return m_regs.r32[14]; + return REGS.r32[14]; } case 28: // IRGB case 29: // ORGB { // ORGB register, convert 16-bit to 555 - const u8 r = static_cast(std::clamp(m_regs.IR1 / 0x80, 0x00, 0x1F)); - const u8 g = static_cast(std::clamp(m_regs.IR2 / 0x80, 0x00, 0x1F)); - const u8 b = static_cast(std::clamp(m_regs.IR3 / 0x80, 0x00, 0x1F)); + const u8 r = static_cast(std::clamp(REGS.IR1 / 0x80, 0x00, 0x1F)); + const u8 g = static_cast(std::clamp(REGS.IR2 / 0x80, 0x00, 0x1F)); + const u8 b = static_cast(std::clamp(REGS.IR3 / 0x80, 0x00, 0x1F)); return ZeroExtend32(r) | (ZeroExtend32(g) << 5) | (ZeroExtend32(b) << 10); } default: - return m_regs.r32[index]; + return REGS.r32[index]; } } -void Core::WriteRegister(u32 index, u32 value) +void WriteRegister(u32 index, u32 value) { #if 0 if (index < 32) @@ -89,7 +219,7 @@ void Core::WriteRegister(u32 index, u32 value) case 62: // ZSF4 { // sign-extend z component of vector registers - m_regs.r32[index] = SignExtend32(Truncate16(value)); + REGS.r32[index] = SignExtend32(Truncate16(value)); } break; @@ -100,33 +230,33 @@ void Core::WriteRegister(u32 index, u32 value) case 19: // SZ3 { // zero-extend unsigned values - m_regs.r32[index] = ZeroExtend32(Truncate16(value)); + REGS.r32[index] = ZeroExtend32(Truncate16(value)); } break; case 15: // SXY3 { // writing to SXYP pushes to the FIFO - m_regs.r32[12] = m_regs.r32[13]; // SXY0 <- SXY1 - m_regs.r32[13] = m_regs.r32[14]; // SXY1 <- SXY2 - m_regs.r32[14] = value; // SXY2 <- SXYP + REGS.r32[12] = REGS.r32[13]; // SXY0 <- SXY1 + REGS.r32[13] = REGS.r32[14]; // SXY1 <- SXY2 + REGS.r32[14] = value; // SXY2 <- SXYP } break; case 28: // IRGB { // IRGB register, convert 555 to 16-bit - m_regs.IRGB = value & UINT32_C(0x7FFF); - m_regs.r32[9] = SignExtend32(static_cast(Truncate16((value & UINT32_C(0x1F)) * UINT32_C(0x80)))); - m_regs.r32[10] = SignExtend32(static_cast(Truncate16(((value >> 5) & UINT32_C(0x1F)) * UINT32_C(0x80)))); - m_regs.r32[11] = SignExtend32(static_cast(Truncate16(((value >> 10) & UINT32_C(0x1F)) * UINT32_C(0x80)))); + REGS.IRGB = value & UINT32_C(0x7FFF); + REGS.r32[9] = SignExtend32(static_cast(Truncate16((value & UINT32_C(0x1F)) * UINT32_C(0x80)))); + REGS.r32[10] = SignExtend32(static_cast(Truncate16(((value >> 5) & UINT32_C(0x1F)) * UINT32_C(0x80)))); + REGS.r32[11] = SignExtend32(static_cast(Truncate16(((value >> 10) & UINT32_C(0x1F)) * UINT32_C(0x80)))); } break; case 30: // LZCS { - m_regs.LZCS = static_cast(value); - m_regs.LZCR = CountLeadingBits(value); + REGS.LZCS = static_cast(value); + REGS.LZCR = CountLeadingBits(value); } break; @@ -139,24 +269,724 @@ void Core::WriteRegister(u32 index, u32 value) case 63: // FLAG { - m_regs.FLAG.bits = value & UINT32_C(0x7FFFF000); - m_regs.FLAG.UpdateError(); + REGS.FLAG.bits = value & UINT32_C(0x7FFFF000); + REGS.FLAG.UpdateError(); } break; default: { // written as-is, 2x16 or 1x32 bits - m_regs.r32[index] = value; + REGS.r32[index] = value; } break; } } -void Core::ExecuteInstruction(Instruction inst) +u32* GetRegisterPtr(u32 index) { - // Panic("GTE instruction"); + return ®S.r32[index]; +} +static void SetOTZ(s32 value) +{ + if (value < 0) + { + REGS.FLAG.sz1_otz_saturated = true; + value = 0; + } + else if (value > 0xFFFF) + { + REGS.FLAG.sz1_otz_saturated = true; + value = 0xFFFF; + } + + REGS.dr32[7] = static_cast(value); +} + +static void PushSXY(s32 x, s32 y) +{ + if (x < -1024) + { + REGS.FLAG.sx2_saturated = true; + x = -1024; + } + else if (x > 1023) + { + REGS.FLAG.sx2_saturated = true; + x = 1023; + } + + if (y < -1024) + { + REGS.FLAG.sy2_saturated = true; + y = -1024; + } + else if (y > 1023) + { + REGS.FLAG.sy2_saturated = true; + y = 1023; + } + + REGS.dr32[12] = REGS.dr32[13]; // SXY0 <- SXY1 + REGS.dr32[13] = REGS.dr32[14]; // SXY1 <- SXY2 + REGS.dr32[14] = (static_cast(x) & 0xFFFFu) | (static_cast(y) << 16); +} + +static void PushSZ(s32 value) +{ + if (value < 0) + { + REGS.FLAG.sz1_otz_saturated = true; + value = 0; + } + else if (value > 0xFFFF) + { + REGS.FLAG.sz1_otz_saturated = true; + value = 0xFFFF; + } + + REGS.dr32[16] = REGS.dr32[17]; // SZ0 <- SZ1 + REGS.dr32[17] = REGS.dr32[18]; // SZ1 <- SZ2 + REGS.dr32[18] = REGS.dr32[19]; // SZ2 <- SZ3 + REGS.dr32[19] = static_cast(value); // SZ3 <- value +} + +static void PushRGBFromMAC() +{ + // Note: SHR 4 used instead of /16 as the results are different. + const u32 r = TruncateRGB<0>(static_cast(REGS.MAC1 >> 4)); + const u32 g = TruncateRGB<1>(static_cast(REGS.MAC2 >> 4)); + const u32 b = TruncateRGB<2>(static_cast(REGS.MAC3 >> 4)); + const u32 c = ZeroExtend32(REGS.RGBC[3]); + + REGS.dr32[20] = REGS.dr32[21]; // RGB0 <- RGB1 + REGS.dr32[21] = REGS.dr32[22]; // RGB1 <- RGB2 + REGS.dr32[22] = r | (g << 8) | (b << 16) | (c << 24); // RGB2 <- Value +} + +static u32 UNRDivide(u32 lhs, u32 rhs) +{ + if (rhs * 2 <= lhs) + { + REGS.FLAG.divide_overflow = true; + return 0x1FFFF; + } + + const u32 shift = (rhs == 0) ? 16 : CountLeadingZeros(static_cast(rhs)); + lhs <<= shift; + rhs <<= shift; + + static constexpr std::array unr_table = {{ + 0xFF, 0xFD, 0xFB, 0xF9, 0xF7, 0xF5, 0xF3, 0xF1, 0xEF, 0xEE, 0xEC, 0xEA, 0xE8, 0xE6, 0xE4, 0xE3, // + 0xE1, 0xDF, 0xDD, 0xDC, 0xDA, 0xD8, 0xD6, 0xD5, 0xD3, 0xD1, 0xD0, 0xCE, 0xCD, 0xCB, 0xC9, 0xC8, // 00h..3Fh + 0xC6, 0xC5, 0xC3, 0xC1, 0xC0, 0xBE, 0xBD, 0xBB, 0xBA, 0xB8, 0xB7, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, // + 0xAE, 0xAD, 0xAB, 0xAA, 0xA9, 0xA7, 0xA6, 0xA4, 0xA3, 0xA2, 0xA0, 0x9F, 0x9E, 0x9C, 0x9B, 0x9A, // + 0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90, 0x8F, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x87, 0x86, // + 0x85, 0x84, 0x83, 0x82, 0x81, 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x75, 0x74, // 40h..7Fh + 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, // + 0x63, 0x62, 0x61, 0x60, 0x5F, 0x5E, 0x5D, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, // + 0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4D, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x48, // + 0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41, 0x40, 0x3F, 0x3F, 0x3E, 0x3D, 0x3C, 0x3C, 0x3B, // 80h..BFh + 0x3A, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35, 0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2F, // + 0x2E, 0x2E, 0x2D, 0x2C, 0x2C, 0x2B, 0x2A, 0x2A, 0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24, // + 0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, 0x1C, 0x1B, 0x1B, 0x1A, // + 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11, // C0h..FFh + 0x10, 0x0F, 0x0F, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C, 0x0B, 0x0A, 0x0A, 0x09, 0x09, 0x08, 0x08, // + 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, // + 0x00 // <-- one extra table entry (for "(d-7FC0h)/80h"=100h) + }}; + + const u32 divisor = rhs | 0x8000; + const s32 x = static_cast(0x101 + ZeroExtend32(unr_table[((divisor & 0x7FFF) + 0x40) >> 7])); + const s32 d = ((static_cast(ZeroExtend32(divisor)) * -x) + 0x80) >> 8; + const u32 recip = static_cast(((x * (0x20000 + d)) + 0x80) >> 8); + + const u32 result = Truncate32((ZeroExtend64(lhs) * ZeroExtend64(recip) + u64(0x8000)) >> 16); + + // The min(1FFFFh) limit is needed for cases like FE3Fh/7F20h, F015h/780Bh, etc. (these do produce UNR result 20000h, + // and are saturated to 1FFFFh, but without setting overflow FLAG bits). + return std::min(0x1FFFF, result); +} + +static void MulMatVec(const s16 M[3][3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm) +{ +#define dot3(i) \ + TruncateAndSetMACAndIR(SignExtendMACResult((s64(M[i][0]) * s64(Vx)) + (s64(M[i][1]) * s64(Vy))) + \ + (s64(M[i][2]) * s64(Vz)), \ + shift, lm) + + dot3(0); + dot3(1); + dot3(2); + +#undef dot3 +} + +static void MulMatVec(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm) +{ +#define dot3(i) \ + TruncateAndSetMACAndIR( \ + SignExtendMACResult(SignExtendMACResult((s64(T[i]) << 12) + (s64(M[i][0]) * s64(Vx))) + \ + (s64(M[i][1]) * s64(Vy))) + \ + (s64(M[i][2]) * s64(Vz)), \ + shift, lm) + + dot3(0); + dot3(1); + dot3(2); + +#undef dot3 +} + +static void MulMatVecBuggy(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, + bool lm) +{ +#define dot3(i) \ + do \ + { \ + TruncateAndSetIR(static_cast(SignExtendMACResult(SignExtendMACResult( \ + (s64(T[i]) << 12) + (s64(M[i][0]) * s64(Vx)))) >> \ + shift), \ + false); \ + TruncateAndSetMACAndIR(SignExtendMACResult((s64(M[i][1]) * s64(Vy))) + (s64(M[i][2]) * s64(Vz)), \ + shift, lm); \ + } while (0) + + dot3(0); + dot3(1); + dot3(2); + +#undef dot3 +} + +static void Execute_MVMVA(Instruction inst) +{ + REGS.FLAG.Clear(); + + // TODO: Remove memcpy.. + s16 M[3][3]; + switch (inst.mvmva_multiply_matrix) + { + case 0: + std::memcpy(M, REGS.RT, sizeof(s16) * 3 * 3); + break; + case 1: + std::memcpy(M, REGS.LLM, sizeof(s16) * 3 * 3); + break; + case 2: + std::memcpy(M, REGS.LCM, sizeof(s16) * 3 * 3); + break; + default: + { + // buggy + M[0][0] = -static_cast(ZeroExtend16(REGS.RGBC[0]) << 4); + M[0][1] = static_cast(ZeroExtend16(REGS.RGBC[0]) << 4); + M[0][2] = REGS.IR0; + M[1][0] = REGS.RT[0][2]; + M[1][1] = REGS.RT[0][2]; + M[1][2] = REGS.RT[0][2]; + M[2][0] = REGS.RT[1][1]; + M[2][1] = REGS.RT[1][1]; + M[2][2] = REGS.RT[1][1]; + } + break; + } + + s16 Vx, Vy, Vz; + switch (inst.mvmva_multiply_vector) + { + case 0: + Vx = REGS.V0[0]; + Vy = REGS.V0[1]; + Vz = REGS.V0[2]; + break; + case 1: + Vx = REGS.V1[0]; + Vy = REGS.V1[1]; + Vz = REGS.V1[2]; + break; + case 2: + Vx = REGS.V2[0]; + Vy = REGS.V2[1]; + Vz = REGS.V2[2]; + break; + default: + Vx = REGS.IR1; + Vy = REGS.IR2; + Vz = REGS.IR3; + break; + } + + static const s32 zero_T[3] = {}; + switch (inst.mvmva_translation_vector) + { + case 0: + MulMatVec(M, REGS.TR, Vx, Vy, Vz, inst.GetShift(), inst.lm); + break; + case 1: + MulMatVec(M, REGS.BK, Vx, Vy, Vz, inst.GetShift(), inst.lm); + break; + case 2: + MulMatVecBuggy(M, REGS.FC, Vx, Vy, Vz, inst.GetShift(), inst.lm); + break; + default: + MulMatVec(M, zero_T, Vx, Vy, Vz, inst.GetShift(), inst.lm); + break; + } + + REGS.FLAG.UpdateError(); +} + +static void Execute_SQR(Instruction inst) +{ + REGS.FLAG.Clear(); + + // 32-bit multiply for speed - 16x16 isn't >32bit, and we know it won't overflow/underflow. + const u8 shift = inst.GetShift(); + REGS.MAC1 = (s32(REGS.IR1) * s32(REGS.IR1)) >> shift; + REGS.MAC2 = (s32(REGS.IR2) * s32(REGS.IR2)) >> shift; + REGS.MAC3 = (s32(REGS.IR3) * s32(REGS.IR3)) >> shift; + + const bool lm = inst.lm; + TruncateAndSetIR<1>(REGS.MAC1, lm); + TruncateAndSetIR<2>(REGS.MAC2, lm); + TruncateAndSetIR<3>(REGS.MAC3, lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_OP(Instruction inst) +{ + REGS.FLAG.Clear(); + + // Take copies since we overwrite them in each step. + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + const s32 D1 = s32(REGS.RT[0][0]); + const s32 D2 = s32(REGS.RT[1][1]); + const s32 D3 = s32(REGS.RT[2][2]); + const s32 IR1 = s32(REGS.IR1); + const s32 IR2 = s32(REGS.IR2); + const s32 IR3 = s32(REGS.IR3); + + // [MAC1,MAC2,MAC3] = [IR3*D2-IR2*D3, IR1*D3-IR3*D1, IR2*D1-IR1*D2] SAR (sf*12) + // [IR1, IR2, IR3] = [MAC1, MAC2, MAC3]; copy result + TruncateAndSetMACAndIR<1>(s64(IR3 * D2) - s64(IR2 * D3), shift, lm); + TruncateAndSetMACAndIR<2>(s64(IR1 * D3) - s64(IR3 * D1), shift, lm); + TruncateAndSetMACAndIR<3>(s64(IR2 * D1) - s64(IR1 * D2), shift, lm); + + REGS.FLAG.UpdateError(); +} + +static void RTPS(const s16 V[3], u8 shift, bool lm, bool last) +{ +#define dot3(i) \ + SignExtendMACResult(SignExtendMACResult((s64(REGS.TR[i]) << 12) + (s64(REGS.RT[i][0]) * s64(V[0]))) + \ + (s64(REGS.RT[i][1]) * s64(V[1]))) + \ + (s64(REGS.RT[i][2]) * s64(V[2])) + + // IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12) + // IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12) + // IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12) + const s64 x = dot3(0); + const s64 y = dot3(1); + const s64 z = dot3(2); + TruncateAndSetMAC<1>(x, shift); + TruncateAndSetMAC<2>(y, shift); + TruncateAndSetMAC<3>(z, shift); + TruncateAndSetIR<1>(REGS.MAC1, lm); + TruncateAndSetIR<2>(REGS.MAC2, lm); + + // The command does saturate IR1,IR2,IR3 to -8000h..+7FFFh (regardless of lm bit). When using RTP with sf=0, then the + // IR3 saturation flag (FLAG.22) gets set if "MAC3 SAR 12" exceeds -8000h..+7FFFh (although IR3 is saturated + // when "MAC3" exceeds -8000h..+7FFFh). + TruncateAndSetIR<3>(s32(z >> 12), false); + REGS.dr32[11] = std::clamp(REGS.MAC3, lm ? 0 : IR123_MIN_VALUE, IR123_MAX_VALUE); +#undef dot3 + + // SZ3 = MAC3 SAR ((1-sf)*12) ;ScreenZ FIFO 0..+FFFFh + PushSZ(s32(z >> 12)); + + // MAC0=(((H*20000h/SZ3)+1)/2)*IR1+OFX, SX2=MAC0/10000h ;ScrX FIFO -400h..+3FFh + // MAC0=(((H*20000h/SZ3)+1)/2)*IR2+OFY, SY2=MAC0/10000h ;ScrY FIFO -400h..+3FFh + const s64 result = static_cast(ZeroExtend64(UNRDivide(REGS.H, REGS.SZ3))); + + // (4 / 3) / (16 / 9) -> 0.75 -> (3 / 4) + const s64 Sx = g_settings.gpu_widescreen_hack ? + ((((s64(result) * s64(REGS.IR1)) * s64(3)) / s64(4)) + s64(REGS.OFX)) : + (s64(result) * s64(REGS.IR1) + s64(REGS.OFX)); + const s64 Sy = s64(result) * s64(REGS.IR2) + s64(REGS.OFY); + CheckMACOverflow<0>(Sx); + CheckMACOverflow<0>(Sy); + PushSXY(s32(Sx >> 16), s32(Sy >> 16)); + + if (last) + { + // MAC0=(((H*20000h/SZ3)+1)/2)*DQA+DQB, IR0=MAC0/1000h ;Depth cueing 0..+1000h + const s64 Sz = s64(result) * s64(REGS.DQA) + s64(REGS.DQB); + TruncateAndSetMAC<0>(Sz, 0); + TruncateAndSetIR<0>(s32(Sz >> 12), true); + } +} + +static void Execute_RTPS(Instruction inst) +{ + REGS.FLAG.Clear(); + RTPS(REGS.V0, inst.GetShift(), inst.lm, true); + REGS.FLAG.UpdateError(); +} + +static void Execute_RTPT(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + RTPS(REGS.V0, shift, lm, false); + RTPS(REGS.V1, shift, lm, false); + RTPS(REGS.V2, shift, lm, true); + + REGS.FLAG.UpdateError(); +} + +static void Execute_NCLIP(Instruction inst) +{ + // MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1 + REGS.FLAG.Clear(); + + TruncateAndSetMAC<0>(s64(REGS.SXY0[0]) * s64(REGS.SXY1[1]) + s64(REGS.SXY1[0]) * s64(REGS.SXY2[1]) + + s64(REGS.SXY2[0]) * s64(REGS.SXY0[1]) - s64(REGS.SXY0[0]) * s64(REGS.SXY2[1]) - + s64(REGS.SXY1[0]) * s64(REGS.SXY0[1]) - s64(REGS.SXY2[0]) * s64(REGS.SXY1[1]), + 0); + + REGS.FLAG.UpdateError(); +} + +static void Execute_AVSZ3(Instruction inst) +{ + REGS.FLAG.Clear(); + + const s64 result = s64(REGS.ZSF3) * s32(u32(REGS.SZ1) + u32(REGS.SZ2) + u32(REGS.SZ3)); + TruncateAndSetMAC<0>(result, 0); + SetOTZ(s32(result >> 12)); + + REGS.FLAG.UpdateError(); +} + +static void Execute_AVSZ4(Instruction inst) +{ + REGS.FLAG.Clear(); + + const s64 result = s64(REGS.ZSF4) * s32(u32(REGS.SZ0) + u32(REGS.SZ1) + u32(REGS.SZ2) + u32(REGS.SZ3)); + TruncateAndSetMAC<0>(result, 0); + SetOTZ(s32(result >> 12)); + + REGS.FLAG.UpdateError(); +} + +static void InterpolateColor(s64 in_MAC1, s64 in_MAC2, s64 in_MAC3, u8 shift, bool lm) +{ + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 + // [IR1,IR2,IR3] = (([RFC,GFC,BFC] SHL 12) - [MAC1,MAC2,MAC3]) SAR (sf*12) + TruncateAndSetMACAndIR<1>((s64(REGS.FC[0]) << 12) - in_MAC1, shift, false); + TruncateAndSetMACAndIR<2>((s64(REGS.FC[1]) << 12) - in_MAC2, shift, false); + TruncateAndSetMACAndIR<3>((s64(REGS.FC[2]) << 12) - in_MAC3, shift, false); + + // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) + // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) + TruncateAndSetMACAndIR<1>(s64(s32(REGS.IR1) * s32(REGS.IR0)) + in_MAC1, shift, lm); + TruncateAndSetMACAndIR<2>(s64(s32(REGS.IR2) * s32(REGS.IR0)) + in_MAC2, shift, lm); + TruncateAndSetMACAndIR<3>(s64(s32(REGS.IR3) * s32(REGS.IR0)) + in_MAC3, shift, lm); +} + +static void NCS(const s16 V[3], u8 shift, bool lm) +{ + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) + MulMatVec(REGS.LLM, V[0], V[1], V[2], shift, lm); + + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) + MulMatVec(REGS.LCM, REGS.BK, REGS.IR1, REGS.IR2, REGS.IR3, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); +} + +static void Execute_NCS(Instruction inst) +{ + REGS.FLAG.Clear(); + + NCS(REGS.V0, inst.GetShift(), inst.lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_NCT(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + NCS(REGS.V0, shift, lm); + NCS(REGS.V1, shift, lm); + NCS(REGS.V2, shift, lm); + + REGS.FLAG.UpdateError(); +} + +static void NCCS(const s16 V[3], u8 shift, bool lm) +{ + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) + MulMatVec(REGS.LLM, V[0], V[1], V[2], shift, lm); + + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) + MulMatVec(REGS.LCM, REGS.BK, REGS.IR1, REGS.IR2, REGS.IR3, shift, lm); + + // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx + // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) ;<--- for NCDx/NCCx + TruncateAndSetMACAndIR<1>(s64(s32(ZeroExtend32(REGS.RGBC[0])) * s32(REGS.IR1)) << 4, shift, lm); + TruncateAndSetMACAndIR<2>(s64(s32(ZeroExtend32(REGS.RGBC[1])) * s32(REGS.IR2)) << 4, shift, lm); + TruncateAndSetMACAndIR<3>(s64(s32(ZeroExtend32(REGS.RGBC[2])) * s32(REGS.IR3)) << 4, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); +} + +static void Execute_NCCS(Instruction inst) +{ + REGS.FLAG.Clear(); + + NCCS(REGS.V0, inst.GetShift(), inst.lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_NCCT(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + NCCS(REGS.V0, shift, lm); + NCCS(REGS.V1, shift, lm); + NCCS(REGS.V2, shift, lm); + + REGS.FLAG.UpdateError(); +} + +static void NCDS(const s16 V[3], u8 shift, bool lm) +{ + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) + MulMatVec(REGS.LLM, V[0], V[1], V[2], shift, lm); + + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) + MulMatVec(REGS.LCM, REGS.BK, REGS.IR1, REGS.IR2, REGS.IR3, shift, lm); + + // No need to assign these to MAC[1-3], as it'll never overflow. + // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx + const s32 in_MAC1 = (s32(ZeroExtend32(REGS.RGBC[0])) * s32(REGS.IR1)) << 4; + const s32 in_MAC2 = (s32(ZeroExtend32(REGS.RGBC[1])) * s32(REGS.IR2)) << 4; + const s32 in_MAC3 = (s32(ZeroExtend32(REGS.RGBC[2])) * s32(REGS.IR3)) << 4; + + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 ;<--- for NCDx only + InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); +} + +static void Execute_NCDS(Instruction inst) +{ + REGS.FLAG.Clear(); + + NCDS(REGS.V0, inst.GetShift(), inst.lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_NCDT(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + NCDS(REGS.V0, shift, lm); + NCDS(REGS.V1, shift, lm); + NCDS(REGS.V2, shift, lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_CC(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) + MulMatVec(REGS.LCM, REGS.BK, REGS.IR1, REGS.IR2, REGS.IR3, shift, lm); + + // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 + // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) + TruncateAndSetMACAndIR<1>(s64(s32(ZeroExtend32(REGS.RGBC[0])) * s32(REGS.IR1)) << 4, shift, lm); + TruncateAndSetMACAndIR<2>(s64(s32(ZeroExtend32(REGS.RGBC[1])) * s32(REGS.IR2)) << 4, shift, lm); + TruncateAndSetMACAndIR<3>(s64(s32(ZeroExtend32(REGS.RGBC[2])) * s32(REGS.IR3)) << 4, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +static void Execute_CDP(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) + MulMatVec(REGS.LCM, REGS.BK, REGS.IR1, REGS.IR2, REGS.IR3, shift, lm); + + // No need to assign these to MAC[1-3], as it'll never overflow. + // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 + const s32 in_MAC1 = (s32(ZeroExtend32(REGS.RGBC[0])) * s32(REGS.IR1)) << 4; + const s32 in_MAC2 = (s32(ZeroExtend32(REGS.RGBC[1])) * s32(REGS.IR2)) << 4; + const s32 in_MAC3 = (s32(ZeroExtend32(REGS.RGBC[2])) * s32(REGS.IR3)) << 4; + + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 ;<--- for CDP only + // [MAC1, MAC2, MAC3] = [MAC1, MAC2, MAC3] SAR(sf * 12) + InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +static void DPCS(const u8 color[3], u8 shift, bool lm) +{ + // In: [IR1,IR2,IR3]=Vector, FC=Far Color, IR0=Interpolation value, CODE=MSB of RGBC + // [MAC1,MAC2,MAC3] = [R,G,B] SHL 16 ;<--- for DPCS/DPCT + TruncateAndSetMAC<1>((s64(ZeroExtend64(color[0])) << 16), 0); + TruncateAndSetMAC<2>((s64(ZeroExtend64(color[1])) << 16), 0); + TruncateAndSetMAC<3>((s64(ZeroExtend64(color[2])) << 16), 0); + + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 + InterpolateColor(REGS.MAC1, REGS.MAC2, REGS.MAC3, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); +} + +static void Execute_DPCS(Instruction inst) +{ + REGS.FLAG.Clear(); + + DPCS(REGS.RGBC, inst.GetShift(), inst.lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_DPCT(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + for (u32 i = 0; i < 3; i++) + DPCS(REGS.RGB0, shift, lm); + + REGS.FLAG.UpdateError(); +} + +static void Execute_DCPL(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // No need to assign these to MAC[1-3], as it'll never overflow. + // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for DCPL only + const s32 in_MAC1 = (s32(ZeroExtend32(REGS.RGBC[0])) * s32(REGS.IR1)) << 4; + const s32 in_MAC2 = (s32(ZeroExtend32(REGS.RGBC[1])) * s32(REGS.IR2)) << 4; + const s32 in_MAC3 = (s32(ZeroExtend32(REGS.RGBC[2])) * s32(REGS.IR3)) << 4; + + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 + InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +static void Execute_INTPL(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // No need to assign these to MAC[1-3], as it'll never overflow. + // [MAC1,MAC2,MAC3] = [IR1,IR2,IR3] SHL 12 ;<--- for INTPL only + // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 + InterpolateColor(s32(REGS.IR1) << 12, s32(REGS.IR2) << 12, s32(REGS.IR3) << 12, shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +static void Execute_GPL(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SHL (sf*12) ;<--- for GPL only + // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12) + TruncateAndSetMACAndIR<1>((s64(s32(REGS.IR1) * s32(REGS.IR0)) + (s64(REGS.MAC1) << shift)), shift, lm); + TruncateAndSetMACAndIR<2>((s64(s32(REGS.IR2) * s32(REGS.IR0)) + (s64(REGS.MAC2) << shift)), shift, lm); + TruncateAndSetMACAndIR<3>((s64(s32(REGS.IR3) * s32(REGS.IR0)) + (s64(REGS.MAC3) << shift)), shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +static void Execute_GPF(Instruction inst) +{ + REGS.FLAG.Clear(); + + const u8 shift = inst.GetShift(); + const bool lm = inst.lm; + + // [MAC1,MAC2,MAC3] = [0,0,0] ;<--- for GPF only + // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12) + TruncateAndSetMACAndIR<1>(s64(s32(REGS.IR1) * s32(REGS.IR0)), shift, lm); + TruncateAndSetMACAndIR<2>(s64(s32(REGS.IR2) * s32(REGS.IR0)), shift, lm); + TruncateAndSetMACAndIR<3>(s64(s32(REGS.IR3) * s32(REGS.IR0)), shift, lm); + + // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] + PushRGBFromMAC(); + + REGS.FLAG.UpdateError(); +} + +void ExecuteInstruction(u32 inst_bits) +{ + const Instruction inst{inst_bits}; switch (inst.command) { case 0x01: @@ -253,701 +1083,81 @@ void Core::ExecuteInstruction(Instruction inst) } } -void Core::SetOTZ(s32 value) +InstructionImpl GetInstructionImpl(u32 inst_bits) { - if (value < 0) + const Instruction inst{inst_bits}; + switch (inst.command) { - m_regs.FLAG.sz1_otz_saturated = true; - value = 0; - } - else if (value > 0xFFFF) - { - m_regs.FLAG.sz1_otz_saturated = true; - value = 0xFFFF; - } + case 0x01: + return &Execute_RTPS; - m_regs.dr32[7] = static_cast(value); -} + case 0x06: + return &Execute_NCLIP; -void Core::PushSXY(s32 x, s32 y) -{ - if (x < -1024) - { - m_regs.FLAG.sx2_saturated = true; - x = -1024; - } - else if (x > 1023) - { - m_regs.FLAG.sx2_saturated = true; - x = 1023; - } + case 0x0C: + return &Execute_OP; - if (y < -1024) - { - m_regs.FLAG.sy2_saturated = true; - y = -1024; - } - else if (y > 1023) - { - m_regs.FLAG.sy2_saturated = true; - y = 1023; - } + case 0x10: + return &Execute_DPCS; - m_regs.dr32[12] = m_regs.dr32[13]; // SXY0 <- SXY1 - m_regs.dr32[13] = m_regs.dr32[14]; // SXY1 <- SXY2 - m_regs.SXY2[0] = static_cast(x); - m_regs.SXY2[1] = static_cast(y); -} + case 0x11: + return &Execute_INTPL; -void Core::PushSZ(s32 value) -{ - if (value < 0) - { - m_regs.FLAG.sz1_otz_saturated = true; - value = 0; - } - else if (value > 0xFFFF) - { - m_regs.FLAG.sz1_otz_saturated = true; - value = 0xFFFF; - } + case 0x12: + return &Execute_MVMVA; - m_regs.dr32[16] = m_regs.dr32[17]; // SZ0 <- SZ1 - m_regs.dr32[17] = m_regs.dr32[18]; // SZ1 <- SZ2 - m_regs.dr32[18] = m_regs.dr32[19]; // SZ2 <- SZ3 - m_regs.dr32[19] = static_cast(value); // SZ3 <- value -} + case 0x13: + return &Execute_NCDS; -void Core::PushRGBFromMAC() -{ - // Note: SHR 4 used instead of /16 as the results are different. - const u32 r = TruncateRGB<0>(static_cast(m_regs.MAC1 >> 4)); - const u32 g = TruncateRGB<1>(static_cast(m_regs.MAC2 >> 4)); - const u32 b = TruncateRGB<2>(static_cast(m_regs.MAC3 >> 4)); - const u32 c = ZeroExtend32(m_regs.RGBC[3]); + case 0x14: + return &Execute_CDP; - m_regs.dr32[20] = m_regs.dr32[21]; // RGB0 <- RGB1 - m_regs.dr32[21] = m_regs.dr32[22]; // RGB1 <- RGB2 - m_regs.dr32[22] = r | (g << 8) | (b << 16) | (c << 24); // RGB2 <- Value -} + case 0x16: + return &Execute_NCDT; -u32 Core::UNRDivide(u32 lhs, u32 rhs) -{ - if (rhs * 2 <= lhs) - { - m_regs.FLAG.divide_overflow = true; - return 0x1FFFF; - } + case 0x1B: + return &Execute_NCCS; - const u32 shift = (rhs == 0) ? 16 : CountLeadingZeros(static_cast(rhs)); - lhs <<= shift; - rhs <<= shift; + case 0x1C: + return &Execute_CC; - static constexpr std::array unr_table = {{ - 0xFF, 0xFD, 0xFB, 0xF9, 0xF7, 0xF5, 0xF3, 0xF1, 0xEF, 0xEE, 0xEC, 0xEA, 0xE8, 0xE6, 0xE4, 0xE3, // - 0xE1, 0xDF, 0xDD, 0xDC, 0xDA, 0xD8, 0xD6, 0xD5, 0xD3, 0xD1, 0xD0, 0xCE, 0xCD, 0xCB, 0xC9, 0xC8, // 00h..3Fh - 0xC6, 0xC5, 0xC3, 0xC1, 0xC0, 0xBE, 0xBD, 0xBB, 0xBA, 0xB8, 0xB7, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, // - 0xAE, 0xAD, 0xAB, 0xAA, 0xA9, 0xA7, 0xA6, 0xA4, 0xA3, 0xA2, 0xA0, 0x9F, 0x9E, 0x9C, 0x9B, 0x9A, // - 0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90, 0x8F, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x87, 0x86, // - 0x85, 0x84, 0x83, 0x82, 0x81, 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x75, 0x74, // 40h..7Fh - 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, // - 0x63, 0x62, 0x61, 0x60, 0x5F, 0x5E, 0x5D, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, // - 0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4D, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x48, // - 0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41, 0x40, 0x3F, 0x3F, 0x3E, 0x3D, 0x3C, 0x3C, 0x3B, // 80h..BFh - 0x3A, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35, 0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2F, // - 0x2E, 0x2E, 0x2D, 0x2C, 0x2C, 0x2B, 0x2A, 0x2A, 0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24, // - 0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, 0x1C, 0x1B, 0x1B, 0x1A, // - 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11, // C0h..FFh - 0x10, 0x0F, 0x0F, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C, 0x0B, 0x0A, 0x0A, 0x09, 0x09, 0x08, 0x08, // - 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, // - 0x00 // <-- one extra table entry (for "(d-7FC0h)/80h"=100h) - }}; + case 0x1E: + return &Execute_NCS; - const u32 divisor = rhs | 0x8000; - const s32 x = static_cast(0x101 + ZeroExtend32(unr_table[((divisor & 0x7FFF) + 0x40) >> 7])); - const s32 d = ((static_cast(ZeroExtend32(divisor)) * -x) + 0x80) >> 8; - const u32 recip = static_cast(((x * (0x20000 + d)) + 0x80) >> 8); + case 0x20: + return &Execute_NCT; - const u32 result = Truncate32((ZeroExtend64(lhs) * ZeroExtend64(recip) + u64(0x8000)) >> 16); + case 0x28: + return &Execute_SQR; - // The min(1FFFFh) limit is needed for cases like FE3Fh/7F20h, F015h/780Bh, etc. (these do produce UNR result 20000h, - // and are saturated to 1FFFFh, but without setting overflow FLAG bits). - return std::min(0x1FFFF, result); -} + case 0x29: + return &Execute_DCPL; -void Core::MulMatVec(const s16 M[3][3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm) -{ -#define dot3(i) \ - TruncateAndSetMACAndIR(SignExtendMACResult((s64(M[i][0]) * s64(Vx)) + (s64(M[i][1]) * s64(Vy))) + \ - (s64(M[i][2]) * s64(Vz)), \ - shift, lm) + case 0x2A: + return &Execute_DPCT; - dot3(0); - dot3(1); - dot3(2); + case 0x2D: + return &Execute_AVSZ3; -#undef dot3 -} + case 0x2E: + return &Execute_AVSZ4; -void Core::MulMatVec(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm) -{ -#define dot3(i) \ - TruncateAndSetMACAndIR( \ - SignExtendMACResult(SignExtendMACResult((s64(T[i]) << 12) + (s64(M[i][0]) * s64(Vx))) + \ - (s64(M[i][1]) * s64(Vy))) + \ - (s64(M[i][2]) * s64(Vz)), \ - shift, lm) + case 0x30: + return &Execute_RTPT; - dot3(0); - dot3(1); - dot3(2); + case 0x3D: + return &Execute_GPF; -#undef dot3 -} + case 0x3E: + return &Execute_GPL; -void Core::MulMatVecBuggy(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, - bool lm) -{ -#define dot3(i) \ - do \ - { \ - TruncateAndSetIR(static_cast(SignExtendMACResult(SignExtendMACResult( \ - (s64(T[i]) << 12) + (s64(M[i][0]) * s64(Vx)))) >> \ - shift), \ - false); \ - TruncateAndSetMACAndIR(SignExtendMACResult((s64(M[i][1]) * s64(Vy))) + (s64(M[i][2]) * s64(Vz)), \ - shift, lm); \ - } while (0) + case 0x3F: + return &Execute_NCCT; - dot3(0); - dot3(1); - dot3(2); - -#undef dot3 -} - -void Core::Execute_MVMVA(Instruction inst) -{ - m_regs.FLAG.Clear(); - - // TODO: Remove memcpy.. - s16 M[3][3]; - switch (inst.mvmva_multiply_matrix) - { - case 0: - std::memcpy(M, m_regs.RT, sizeof(s16) * 3 * 3); - break; - case 1: - std::memcpy(M, m_regs.LLM, sizeof(s16) * 3 * 3); - break; - case 2: - std::memcpy(M, m_regs.LCM, sizeof(s16) * 3 * 3); - break; default: - { - // buggy - M[0][0] = -static_cast(ZeroExtend16(m_regs.RGBC[0]) << 4); - M[0][1] = static_cast(ZeroExtend16(m_regs.RGBC[0]) << 4); - M[0][2] = m_regs.IR0; - M[1][0] = m_regs.RT[0][2]; - M[1][1] = m_regs.RT[0][2]; - M[1][2] = m_regs.RT[0][2]; - M[2][0] = m_regs.RT[1][1]; - M[2][1] = m_regs.RT[1][1]; - M[2][2] = m_regs.RT[1][1]; - } - break; + Panic("Missing handler"); + return nullptr; } - - s16 Vx, Vy, Vz; - switch (inst.mvmva_multiply_vector) - { - case 0: - Vx = m_regs.V0[0]; - Vy = m_regs.V0[1]; - Vz = m_regs.V0[2]; - break; - case 1: - Vx = m_regs.V1[0]; - Vy = m_regs.V1[1]; - Vz = m_regs.V1[2]; - break; - case 2: - Vx = m_regs.V2[0]; - Vy = m_regs.V2[1]; - Vz = m_regs.V2[2]; - break; - default: - Vx = m_regs.IR1; - Vy = m_regs.IR2; - Vz = m_regs.IR3; - break; - } - - static const s32 zero_T[3] = {}; - switch (inst.mvmva_translation_vector) - { - case 0: - MulMatVec(M, m_regs.TR, Vx, Vy, Vz, inst.GetShift(), inst.lm); - break; - case 1: - MulMatVec(M, m_regs.BK, Vx, Vy, Vz, inst.GetShift(), inst.lm); - break; - case 2: - MulMatVecBuggy(M, m_regs.FC, Vx, Vy, Vz, inst.GetShift(), inst.lm); - break; - default: - MulMatVec(M, zero_T, Vx, Vy, Vz, inst.GetShift(), inst.lm); - break; - } - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_SQR(Instruction inst) -{ - m_regs.FLAG.Clear(); - - // 32-bit multiply for speed - 16x16 isn't >32bit, and we know it won't overflow/underflow. - const u8 shift = inst.GetShift(); - m_regs.MAC1 = (s32(m_regs.IR1) * s32(m_regs.IR1)) >> shift; - m_regs.MAC2 = (s32(m_regs.IR2) * s32(m_regs.IR2)) >> shift; - m_regs.MAC3 = (s32(m_regs.IR3) * s32(m_regs.IR3)) >> shift; - - const bool lm = inst.lm; - TruncateAndSetIR<1>(m_regs.MAC1, lm); - TruncateAndSetIR<2>(m_regs.MAC2, lm); - TruncateAndSetIR<3>(m_regs.MAC3, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_OP(Instruction inst) -{ - m_regs.FLAG.Clear(); - - // Take copies since we overwrite them in each step. - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - const s32 D1 = s32(m_regs.RT[0][0]); - const s32 D2 = s32(m_regs.RT[1][1]); - const s32 D3 = s32(m_regs.RT[2][2]); - const s32 IR1 = s32(m_regs.IR1); - const s32 IR2 = s32(m_regs.IR2); - const s32 IR3 = s32(m_regs.IR3); - - // [MAC1,MAC2,MAC3] = [IR3*D2-IR2*D3, IR1*D3-IR3*D1, IR2*D1-IR1*D2] SAR (sf*12) - // [IR1, IR2, IR3] = [MAC1, MAC2, MAC3]; copy result - TruncateAndSetMACAndIR<1>(s64(IR3 * D2) - s64(IR2 * D3), shift, lm); - TruncateAndSetMACAndIR<2>(s64(IR1 * D3) - s64(IR3 * D1), shift, lm); - TruncateAndSetMACAndIR<3>(s64(IR2 * D1) - s64(IR1 * D2), shift, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::RTPS(const s16 V[3], u8 shift, bool lm, bool last) -{ -#define dot3(i) \ - SignExtendMACResult( \ - SignExtendMACResult((s64(m_regs.TR[i]) << 12) + (s64(m_regs.RT[i][0]) * s64(V[0]))) + \ - (s64(m_regs.RT[i][1]) * s64(V[1]))) + \ - (s64(m_regs.RT[i][2]) * s64(V[2])) - - // IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12) - // IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12) - // IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12) - const s64 x = dot3(0); - const s64 y = dot3(1); - const s64 z = dot3(2); - TruncateAndSetMAC<1>(x, shift); - TruncateAndSetMAC<2>(y, shift); - TruncateAndSetMAC<3>(z, shift); - TruncateAndSetIR<1>(m_regs.MAC1, lm); - TruncateAndSetIR<2>(m_regs.MAC2, lm); - - // The command does saturate IR1,IR2,IR3 to -8000h..+7FFFh (regardless of lm bit). When using RTP with sf=0, then the - // IR3 saturation flag (FLAG.22) gets set if "MAC3 SAR 12" exceeds -8000h..+7FFFh (although IR3 is saturated - // when "MAC3" exceeds -8000h..+7FFFh). - TruncateAndSetIR<3>(s32(z >> 12), false); - m_regs.dr32[11] = std::clamp(m_regs.MAC3, lm ? 0 : IR123_MIN_VALUE, IR123_MAX_VALUE); -#undef dot3 - - // SZ3 = MAC3 SAR ((1-sf)*12) ;ScreenZ FIFO 0..+FFFFh - PushSZ(s32(z >> 12)); - - // MAC0=(((H*20000h/SZ3)+1)/2)*IR1+OFX, SX2=MAC0/10000h ;ScrX FIFO -400h..+3FFh - // MAC0=(((H*20000h/SZ3)+1)/2)*IR2+OFY, SY2=MAC0/10000h ;ScrY FIFO -400h..+3FFh - const s64 result = static_cast(ZeroExtend64(UNRDivide(m_regs.H, m_regs.SZ3))); - - // (4 / 3) / (16 / 9) -> 0.75 -> (3 / 4) - const s64 Sx = m_widescreen_hack ? ((((s64(result) * s64(m_regs.IR1)) * s64(3)) / s64(4)) + s64(m_regs.OFX)) : - (s64(result) * s64(m_regs.IR1) + s64(m_regs.OFX)); - const s64 Sy = s64(result) * s64(m_regs.IR2) + s64(m_regs.OFY); - CheckMACOverflow<0>(Sx); - CheckMACOverflow<0>(Sy); - PushSXY(s32(Sx >> 16), s32(Sy >> 16)); - - if (last) - { - // MAC0=(((H*20000h/SZ3)+1)/2)*DQA+DQB, IR0=MAC0/1000h ;Depth cueing 0..+1000h - const s64 Sz = s64(result) * s64(m_regs.DQA) + s64(m_regs.DQB); - TruncateAndSetMAC<0>(Sz, 0); - TruncateAndSetIR<0>(s32(Sz >> 12), true); - } -} - -void Core::Execute_RTPS(Instruction inst) -{ - m_regs.FLAG.Clear(); - RTPS(m_regs.V0, inst.GetShift(), inst.lm, true); - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_RTPT(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - RTPS(m_regs.V0, shift, lm, false); - RTPS(m_regs.V1, shift, lm, false); - RTPS(m_regs.V2, shift, lm, true); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_NCLIP(Instruction inst) -{ - // MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1 - m_regs.FLAG.Clear(); - - TruncateAndSetMAC<0>(s64(m_regs.SXY0[0]) * s64(m_regs.SXY1[1]) + s64(m_regs.SXY1[0]) * s64(m_regs.SXY2[1]) + - s64(m_regs.SXY2[0]) * s64(m_regs.SXY0[1]) - s64(m_regs.SXY0[0]) * s64(m_regs.SXY2[1]) - - s64(m_regs.SXY1[0]) * s64(m_regs.SXY0[1]) - s64(m_regs.SXY2[0]) * s64(m_regs.SXY1[1]), - 0); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_AVSZ3(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const s64 result = s64(m_regs.ZSF3) * s32(u32(m_regs.SZ1) + u32(m_regs.SZ2) + u32(m_regs.SZ3)); - TruncateAndSetMAC<0>(result, 0); - SetOTZ(s32(result >> 12)); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_AVSZ4(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const s64 result = s64(m_regs.ZSF4) * s32(u32(m_regs.SZ0) + u32(m_regs.SZ1) + u32(m_regs.SZ2) + u32(m_regs.SZ3)); - TruncateAndSetMAC<0>(result, 0); - SetOTZ(s32(result >> 12)); - - m_regs.FLAG.UpdateError(); -} - -void Core::InterpolateColor(s64 in_MAC1, s64 in_MAC2, s64 in_MAC3, u8 shift, bool lm) -{ - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 - // [IR1,IR2,IR3] = (([RFC,GFC,BFC] SHL 12) - [MAC1,MAC2,MAC3]) SAR (sf*12) - TruncateAndSetMACAndIR<1>((s64(m_regs.FC[0]) << 12) - in_MAC1, shift, false); - TruncateAndSetMACAndIR<2>((s64(m_regs.FC[1]) << 12) - in_MAC2, shift, false); - TruncateAndSetMACAndIR<3>((s64(m_regs.FC[2]) << 12) - in_MAC3, shift, false); - - // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) - // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) - TruncateAndSetMACAndIR<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + in_MAC1, shift, lm); - TruncateAndSetMACAndIR<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + in_MAC2, shift, lm); - TruncateAndSetMACAndIR<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + in_MAC3, shift, lm); -} - -void Core::NCS(const s16 V[3], u8 shift, bool lm) -{ - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) - MulMatVec(m_regs.LLM, V[0], V[1], V[2], shift, lm); - - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) - MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); -} - -void Core::Execute_NCS(Instruction inst) -{ - m_regs.FLAG.Clear(); - - NCS(m_regs.V0, inst.GetShift(), inst.lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_NCT(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - NCS(m_regs.V0, shift, lm); - NCS(m_regs.V1, shift, lm); - NCS(m_regs.V2, shift, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::NCCS(const s16 V[3], u8 shift, bool lm) -{ - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) - MulMatVec(m_regs.LLM, V[0], V[1], V[2], shift, lm); - - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) - MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm); - - // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx - // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) ;<--- for NCDx/NCCx - TruncateAndSetMACAndIR<1>(s64(s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4, shift, lm); - TruncateAndSetMACAndIR<2>(s64(s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4, shift, lm); - TruncateAndSetMACAndIR<3>(s64(s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); -} - -void Core::Execute_NCCS(Instruction inst) -{ - m_regs.FLAG.Clear(); - - NCCS(m_regs.V0, inst.GetShift(), inst.lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_NCCT(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - NCCS(m_regs.V0, shift, lm); - NCCS(m_regs.V1, shift, lm); - NCCS(m_regs.V2, shift, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::NCDS(const s16 V[3], u8 shift, bool lm) -{ - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (LLM*V0) SAR (sf*12) - MulMatVec(m_regs.LLM, V[0], V[1], V[2], shift, lm); - - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) - MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm); - - // No need to assign these to MAC[1-3], as it'll never overflow. - // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for NCDx/NCCx - const s32 in_MAC1 = (s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4; - const s32 in_MAC2 = (s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4; - const s32 in_MAC3 = (s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4; - - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 ;<--- for NCDx only - InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); -} - -void Core::Execute_NCDS(Instruction inst) -{ - m_regs.FLAG.Clear(); - - NCDS(m_regs.V0, inst.GetShift(), inst.lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_NCDT(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - NCDS(m_regs.V0, shift, lm); - NCDS(m_regs.V1, shift, lm); - NCDS(m_regs.V2, shift, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_CC(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) - MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm); - - // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 - // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SAR (sf*12) - TruncateAndSetMACAndIR<1>(s64(s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4, shift, lm); - TruncateAndSetMACAndIR<2>(s64(s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4, shift, lm); - TruncateAndSetMACAndIR<3>(s64(s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_CDP(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] = (BK*1000h + LCM*IR) SAR (sf*12) - MulMatVec(m_regs.LCM, m_regs.BK, m_regs.IR1, m_regs.IR2, m_regs.IR3, shift, lm); - - // No need to assign these to MAC[1-3], as it'll never overflow. - // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 - const s32 in_MAC1 = (s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4; - const s32 in_MAC2 = (s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4; - const s32 in_MAC3 = (s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4; - - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 ;<--- for CDP only - // [MAC1, MAC2, MAC3] = [MAC1, MAC2, MAC3] SAR(sf * 12) - InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); -} - -void Core::DPCS(const u8 color[3], u8 shift, bool lm) -{ - // In: [IR1,IR2,IR3]=Vector, FC=Far Color, IR0=Interpolation value, CODE=MSB of RGBC - // [MAC1,MAC2,MAC3] = [R,G,B] SHL 16 ;<--- for DPCS/DPCT - TruncateAndSetMAC<1>((s64(ZeroExtend64(color[0])) << 16), 0); - TruncateAndSetMAC<2>((s64(ZeroExtend64(color[1])) << 16), 0); - TruncateAndSetMAC<3>((s64(ZeroExtend64(color[2])) << 16), 0); - - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 - InterpolateColor(m_regs.MAC1, m_regs.MAC2, m_regs.MAC3, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); -} - -void Core::Execute_DPCS(Instruction inst) -{ - m_regs.FLAG.Clear(); - - DPCS(m_regs.RGBC, inst.GetShift(), inst.lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_DPCT(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - for (u32 i = 0; i < 3; i++) - DPCS(m_regs.RGB0, shift, lm); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_DCPL(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // No need to assign these to MAC[1-3], as it'll never overflow. - // [MAC1,MAC2,MAC3] = [R*IR1,G*IR2,B*IR3] SHL 4 ;<--- for DCPL only - const s32 in_MAC1 = (s32(ZeroExtend32(m_regs.RGBC[0])) * s32(m_regs.IR1)) << 4; - const s32 in_MAC2 = (s32(ZeroExtend32(m_regs.RGBC[1])) * s32(m_regs.IR2)) << 4; - const s32 in_MAC3 = (s32(ZeroExtend32(m_regs.RGBC[2])) * s32(m_regs.IR3)) << 4; - - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 - InterpolateColor(in_MAC1, in_MAC2, in_MAC3, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_INTPL(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // No need to assign these to MAC[1-3], as it'll never overflow. - // [MAC1,MAC2,MAC3] = [IR1,IR2,IR3] SHL 12 ;<--- for INTPL only - // [MAC1,MAC2,MAC3] = MAC+(FC-MAC)*IR0 - InterpolateColor(s32(m_regs.IR1) << 12, s32(m_regs.IR2) << 12, s32(m_regs.IR3) << 12, shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_GPL(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // [MAC1,MAC2,MAC3] = [MAC1,MAC2,MAC3] SHL (sf*12) ;<--- for GPL only - // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12) - TruncateAndSetMACAndIR<1>((s64(s32(m_regs.IR1) * s32(m_regs.IR0)) + (s64(m_regs.MAC1) << shift)), shift, lm); - TruncateAndSetMACAndIR<2>((s64(s32(m_regs.IR2) * s32(m_regs.IR0)) + (s64(m_regs.MAC2) << shift)), shift, lm); - TruncateAndSetMACAndIR<3>((s64(s32(m_regs.IR3) * s32(m_regs.IR0)) + (s64(m_regs.MAC3) << shift)), shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); -} - -void Core::Execute_GPF(Instruction inst) -{ - m_regs.FLAG.Clear(); - - const u8 shift = inst.GetShift(); - const bool lm = inst.lm; - - // [MAC1,MAC2,MAC3] = [0,0,0] ;<--- for GPF only - // [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12) - TruncateAndSetMACAndIR<1>(s64(s32(m_regs.IR1) * s32(m_regs.IR0)), shift, lm); - TruncateAndSetMACAndIR<2>(s64(s32(m_regs.IR2) * s32(m_regs.IR0)), shift, lm); - TruncateAndSetMACAndIR<3>(s64(s32(m_regs.IR3) * s32(m_regs.IR0)), shift, lm); - - // Color FIFO = [MAC1/16,MAC2/16,MAC3/16,CODE], [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] - PushRGBFromMAC(); - - m_regs.FLAG.UpdateError(); } } // namespace GTE diff --git a/src/core/gte.h b/src/core/gte.h index bdc37b295..716148c13 100644 --- a/src/core/gte.h +++ b/src/core/gte.h @@ -1,119 +1,24 @@ #pragma once -#include "common/state_wrapper.h" #include "gte_types.h" -namespace CPU { -class Core; - -namespace Recompiler { -class CodeGenerator; -} -} // namespace CPU +class StateWrapper; namespace GTE { -class Core -{ -public: - friend CPU::Core; - friend CPU::Recompiler::CodeGenerator; +void Initialize(); +void Reset(); +bool DoState(StateWrapper& sw); - Core(); - ~Core(); +// control registers are offset by +32 +u32 ReadRegister(u32 index); +void WriteRegister(u32 index, u32 value); - ALWAYS_INLINE void SetWidescreenHack(bool enabled) { m_widescreen_hack = enabled; } +// use with care, direct register access +u32* GetRegisterPtr(u32 index); - void Initialize(); - void Reset(); - bool DoState(StateWrapper& sw); +void ExecuteInstruction(u32 inst_bits); - // control registers are offset by +32 - u32 ReadRegister(u32 index) const; - void WriteRegister(u32 index, u32 value); +using InstructionImpl = void (*)(Instruction); +InstructionImpl GetInstructionImpl(u32 inst_bits); - void ExecuteInstruction(Instruction inst); - -private: - static constexpr s64 MAC0_MIN_VALUE = -(INT64_C(1) << 31); - static constexpr s64 MAC0_MAX_VALUE = (INT64_C(1) << 31) - 1; - static constexpr s64 MAC123_MIN_VALUE = -(INT64_C(1) << 43); - static constexpr s64 MAC123_MAX_VALUE = (INT64_C(1) << 43) - 1; - static constexpr s32 IR0_MIN_VALUE = 0x0000; - static constexpr s32 IR0_MAX_VALUE = 0x1000; - static constexpr s32 IR123_MIN_VALUE = -(INT64_C(1) << 15); - static constexpr s32 IR123_MAX_VALUE = (INT64_C(1) << 15) - 1; - - // Checks for underflow/overflow. - template - void CheckMACOverflow(s64 value); - - // Checks for underflow/overflow, sign-extending to 31/43 bits. - template - s64 SignExtendMACResult(s64 value); - - template - void TruncateAndSetMAC(s64 value, u8 shift); - - template - void TruncateAndSetMACAndIR(s64 value, u8 shift, bool lm); - - template - void TruncateAndSetIR(s32 value, bool lm); - - template - u32 TruncateRGB(s32 value); - - void SetOTZ(s32 value); - void PushSXY(s32 x, s32 y); - void PushSZ(s32 value); - void PushRGBFromMAC(); - - // Divide using Unsigned Newton-Raphson algorithm. - u32 UNRDivide(u32 lhs, u32 rhs); - - // 3x3 matrix * 3x1 vector, updates MAC[1-3] and IR[1-3] - void MulMatVec(const s16 M[3][3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); - - // 3x3 matrix * 3x1 vector with translation, updates MAC[1-3] and IR[1-3] - void MulMatVec(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); - void MulMatVecBuggy(const s16 M[3][3], const s32 T[3], const s16 Vx, const s16 Vy, const s16 Vz, u8 shift, bool lm); - - // Interpolate colour, or as in nocash "MAC+(FC-MAC)*IR0". - void InterpolateColor(s64 in_MAC1, s64 in_MAC2, s64 in_MAC3, u8 shift, bool lm); - - void RTPS(const s16 V[3], u8 shift, bool lm, bool last); - void NCS(const s16 V[3], u8 shift, bool lm); - void NCCS(const s16 V[3], u8 shift, bool lm); - void NCDS(const s16 V[3], u8 shift, bool lm); - void DPCS(const u8 color[3], u8 shift, bool lm); - - void Execute_MVMVA(Instruction inst); - void Execute_SQR(Instruction inst); - void Execute_OP(Instruction inst); - void Execute_RTPS(Instruction inst); - void Execute_RTPT(Instruction inst); - void Execute_NCLIP(Instruction inst); - void Execute_AVSZ3(Instruction inst); - void Execute_AVSZ4(Instruction inst); - void Execute_NCS(Instruction inst); - void Execute_NCT(Instruction inst); - void Execute_NCCS(Instruction inst); - void Execute_NCCT(Instruction inst); - void Execute_NCDS(Instruction inst); - void Execute_NCDT(Instruction inst); - void Execute_CC(Instruction inst); - void Execute_CDP(Instruction inst); - void Execute_DPCS(Instruction inst); - void Execute_DPCT(Instruction inst); - void Execute_DCPL(Instruction inst); - void Execute_INTPL(Instruction inst); - void Execute_GPL(Instruction inst); - void Execute_GPF(Instruction inst); - - Regs m_regs = {}; - bool m_widescreen_hack = false; -}; - -#include "gte.inl" - -} // namespace GTE \ No newline at end of file +} // namespace GTE diff --git a/src/core/gte.inl b/src/core/gte.inl deleted file mode 100644 index 733558a2f..000000000 --- a/src/core/gte.inl +++ /dev/null @@ -1,117 +0,0 @@ -#include "gte.h" - -template -void GTE::Core::CheckMACOverflow(s64 value) -{ - constexpr s64 MIN_VALUE = (index == 0) ? MAC0_MIN_VALUE : MAC123_MIN_VALUE; - constexpr s64 MAX_VALUE = (index == 0) ? MAC0_MAX_VALUE : MAC123_MAX_VALUE; - if (value < MIN_VALUE) - { - if constexpr (index == 0) - m_regs.FLAG.mac0_underflow = true; - else if constexpr (index == 1) - m_regs.FLAG.mac1_underflow = true; - else if constexpr (index == 2) - m_regs.FLAG.mac2_underflow = true; - else if constexpr (index == 3) - m_regs.FLAG.mac3_underflow = true; - } - else if (value > MAX_VALUE) - { - if constexpr (index == 0) - m_regs.FLAG.mac0_overflow = true; - else if constexpr (index == 1) - m_regs.FLAG.mac1_overflow = true; - else if constexpr (index == 2) - m_regs.FLAG.mac2_overflow = true; - else if constexpr (index == 3) - m_regs.FLAG.mac3_overflow = true; - } -} - -template -s64 GTE::Core::SignExtendMACResult(s64 value) -{ - CheckMACOverflow(value); - return SignExtendN < index == 0 ? 31 : 44 > (value); -} - -template -void GTE::Core::TruncateAndSetMAC(s64 value, u8 shift) -{ - CheckMACOverflow(value); - - // shift should be done before storing to avoid losing precision - value >>= shift; - - m_regs.dr32[24 + index] = Truncate32(static_cast(value)); -} - -template -void GTE::Core::TruncateAndSetIR(s32 value, bool lm) -{ - constexpr s32 MIN_VALUE = (index == 0) ? IR0_MIN_VALUE : IR123_MIN_VALUE; - constexpr s32 MAX_VALUE = (index == 0) ? IR0_MAX_VALUE : IR123_MAX_VALUE; - const s32 actual_min_value = lm ? 0 : MIN_VALUE; - if (value < actual_min_value) - { - value = actual_min_value; - if constexpr (index == 0) - m_regs.FLAG.ir0_saturated = true; - else if constexpr (index == 1) - m_regs.FLAG.ir1_saturated = true; - else if constexpr (index == 2) - m_regs.FLAG.ir2_saturated = true; - else if constexpr (index == 3) - m_regs.FLAG.ir3_saturated = true; - } - else if (value > MAX_VALUE) - { - value = MAX_VALUE; - if constexpr (index == 0) - m_regs.FLAG.ir0_saturated = true; - else if constexpr (index == 1) - m_regs.FLAG.ir1_saturated = true; - else if constexpr (index == 2) - m_regs.FLAG.ir2_saturated = true; - else if constexpr (index == 3) - m_regs.FLAG.ir3_saturated = true; - } - - // store sign-extended 16-bit value as 32-bit - m_regs.dr32[8 + index] = value; -} - -template -void GTE::Core::TruncateAndSetMACAndIR(s64 value, u8 shift, bool lm) -{ - CheckMACOverflow(value); - - // shift should be done before storing to avoid losing precision - value >>= shift; - - // set MAC - const s32 value32 = static_cast(value); - m_regs.dr32[24 + index] = value32; - - // set IR - TruncateAndSetIR(value32, lm); -} - -template -u32 GTE::Core::TruncateRGB(s32 value) -{ - if (value < 0 || value > 0xFF) - { - if constexpr (index == 0) - m_regs.FLAG.color_r_saturated = true; - else if constexpr (index == 1) - m_regs.FLAG.color_g_saturated = true; - else - m_regs.FLAG.color_b_saturated = true; - - return (value < 0) ? 0 : 0xFF; - } - - return static_cast(value); -} diff --git a/src/core/gte_types.h b/src/core/gte_types.h index ffba0a642..32198fc68 100644 --- a/src/core/gte_types.h +++ b/src/core/gte_types.h @@ -3,9 +3,13 @@ #include "types.h" namespace GTE { -static constexpr u32 NUM_DATA_REGS = 32; -static constexpr u32 NUM_CONTROL_REGS = 32; -static constexpr u32 NUM_REGS = NUM_DATA_REGS + NUM_CONTROL_REGS; + +enum : u32 +{ + NUM_DATA_REGS = 32, + NUM_CONTROL_REGS = 32, + NUM_REGS = NUM_DATA_REGS + NUM_CONTROL_REGS +}; union FLAGS { @@ -138,4 +142,4 @@ union Instruction static constexpr u32 REQUIRED_BITS_MASK = ((1 << 20) - 1); }; -} // namespace GTE \ No newline at end of file +} // namespace GTE diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index bd81def6d..4f0d3c6e6 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -9,8 +9,10 @@ #include "common/string_util.h" #include "controller.h" #include "cpu_core.h" +#include "cpu_code_cache.h" #include "dma.h" #include "gpu.h" +#include "gte.h" #include "host_display.h" #include "save_state_version.h" #include "system.h" @@ -21,8 +23,13 @@ #include Log_SetChannel(HostInterface); +HostInterface* g_host_interface; + HostInterface::HostInterface() { + Assert(!g_host_interface); + g_host_interface = this; + // we can get the program directory at construction time const std::string program_path = FileSystem::GetProgramPath(); m_program_directory = FileSystem::GetPathDirectory(program_path.c_str()); @@ -31,7 +38,9 @@ HostInterface::HostInterface() HostInterface::~HostInterface() { // system should be shut down prior to the destructor - Assert(!m_system && !m_audio_stream && !m_display); + Assert(System::IsShutdown() && !m_audio_stream && !m_display); + Assert(g_host_interface == this); + g_host_interface = nullptr; } bool HostInterface::Initialize() @@ -44,20 +53,20 @@ void HostInterface::Shutdown() {} void HostInterface::CreateAudioStream() { Log_InfoPrintf("Creating '%s' audio stream, sample rate = %u, channels = %u, buffer size = %u", - Settings::GetAudioBackendName(m_settings.audio_backend), AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, - m_settings.audio_buffer_size); + Settings::GetAudioBackendName(g_settings.audio_backend), AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, + g_settings.audio_buffer_size); - m_audio_stream = CreateAudioStream(m_settings.audio_backend); + m_audio_stream = CreateAudioStream(g_settings.audio_backend); - if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size)) + if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, g_settings.audio_buffer_size)) { ReportFormattedError("Failed to create or configure audio stream, falling back to null output."); m_audio_stream.reset(); m_audio_stream = AudioStream::CreateNullAudioStream(); - m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size); + m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, g_settings.audio_buffer_size); } - m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume); + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); } bool HostInterface::BootSystem(const SystemBootParameters& parameters) @@ -78,14 +87,13 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters) } // set host display settings - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); - m_display->SetDisplayIntegerScaling(m_settings.display_integer_scaling); + m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering); + m_display->SetDisplayIntegerScaling(g_settings.display_integer_scaling); // create the audio stream. this will never fail, since we'll just fall back to null CreateAudioStream(); - m_system = System::Create(this); - if (!m_system->Boot(parameters)) + if (!System::Boot(parameters)) { ReportFormattedError("System failed to boot. The log may contain more information."); DestroySystem(); @@ -101,25 +109,22 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters) void HostInterface::ResetSystem() { - m_system->Reset(); - m_system->ResetPerformanceCounters(); + System::Reset(); + System::ResetPerformanceCounters(); AddOSDMessage("System reset."); } void HostInterface::PowerOffSystem() { - if (!m_system) - return; - DestroySystem(); } void HostInterface::DestroySystem() { - if (!m_system) + if (System::IsShutdown()) return; - m_system.reset(); + System::Shutdown(); m_audio_stream.reset(); UpdateSoftwareCursor(); ReleaseHostDisplay(); @@ -210,50 +215,50 @@ std::optional> HostInterface::GetBIOSImage(ConsoleRegion region) } while (0) // Try the configured image. - TRY_FILENAME(m_settings.bios_path.c_str()); + TRY_FILENAME(g_settings.bios_path.c_str()); // Try searching in the same folder for other region's images. switch (region) { case ConsoleRegion::NTSC_J: - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph3000.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-11j.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph1000.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-10j.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph5500.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-30j.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7000.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7500.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph9000.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-40j.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph3000.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-11j.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1000.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-10j.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5500.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30j.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7000.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7500.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9000.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-40j.bin", false, false)); break; case ConsoleRegion::NTSC_U: - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph1001.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-22a.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph5501.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph5503.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7003.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-30a.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7001.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7501.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7503.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph9001.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph9003.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph9903.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-41a.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1001.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-22a.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5501.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5503.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7003.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30a.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7001.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7501.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7503.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9001.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9003.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9903.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-41a.bin", false, false)); break; case ConsoleRegion::PAL: - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph1002.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-21e.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph5502.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph5552.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-30e.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7002.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph7502.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "scph9002.bin", false, false)); - TRY_FILENAME(FileSystem::BuildPathRelativeToFile(m_settings.bios_path.c_str(), "ps-41e.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1002.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-21e.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5502.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5552.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30e.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7002.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7502.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9002.bin", false, false)); + TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-41e.bin", false, false)); break; default: @@ -266,8 +271,8 @@ std::optional> HostInterface::GetBIOSImage(ConsoleRegion region) // Fall back to the default image. Log_WarningPrintf("No suitable BIOS image for region %s could be located, using configured image '%s'. This may " "result in instability.", - Settings::GetConsoleRegionName(region), m_settings.bios_path.c_str()); - return BIOS::LoadImageFromFile(m_settings.bios_path); + Settings::GetConsoleRegionName(region), g_settings.bios_path.c_str()); + return BIOS::LoadImageFromFile(g_settings.bios_path); } bool HostInterface::LoadState(const char* filename) @@ -278,12 +283,12 @@ bool HostInterface::LoadState(const char* filename) AddFormattedOSDMessage(2.0f, "Loading state from '%s'...", filename); - if (m_system) + if (!System::IsShutdown()) { - if (!m_system->LoadState(stream.get())) + if (!System::LoadState(stream.get())) { ReportFormattedError("Loading state from '%s' failed. Resetting.", filename); - m_system->Reset(); + ResetSystem(); return false; } } @@ -295,7 +300,7 @@ bool HostInterface::LoadState(const char* filename) return false; } - m_system->ResetPerformanceCounters(); + System::ResetPerformanceCounters(); return true; } @@ -307,7 +312,7 @@ bool HostInterface::SaveState(const char* filename) if (!stream) return false; - const bool result = m_system->SaveState(stream.get()); + const bool result = System::SaveState(stream.get()); if (!result) { ReportFormattedError("Saving state to '%s' failed.", filename); @@ -422,89 +427,86 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) void HostInterface::LoadSettings(SettingsInterface& si) { - m_settings.Load(si); + g_settings.Load(si); } void HostInterface::SaveSettings(SettingsInterface& si) { - m_settings.Save(si); + g_settings.Save(si); } void HostInterface::CheckForSettingsChanges(const Settings& old_settings) { - if (m_system) + if (!System::IsShutdown()) { - if (m_settings.gpu_renderer != old_settings.gpu_renderer || - m_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device) + if (g_settings.gpu_renderer != old_settings.gpu_renderer || + g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device) { - ReportFormattedMessage("Switching to %s%s GPU renderer.", Settings::GetRendererName(m_settings.gpu_renderer), - m_settings.gpu_use_debug_device ? " (debug)" : ""); + ReportFormattedMessage("Switching to %s%s GPU renderer.", Settings::GetRendererName(g_settings.gpu_renderer), + g_settings.gpu_use_debug_device ? " (debug)" : ""); RecreateSystem(); } - if (m_settings.audio_backend != old_settings.audio_backend || - m_settings.audio_buffer_size != old_settings.audio_buffer_size) + if (g_settings.audio_backend != old_settings.audio_backend || + g_settings.audio_buffer_size != old_settings.audio_buffer_size) { - if (m_settings.audio_backend != old_settings.audio_backend) + if (g_settings.audio_backend != old_settings.audio_backend) ReportFormattedMessage("Switching to %s audio backend.", - Settings::GetAudioBackendName(m_settings.audio_backend)); + Settings::GetAudioBackendName(g_settings.audio_backend)); DebugAssert(m_audio_stream); m_audio_stream.reset(); CreateAudioStream(); - m_audio_stream->PauseOutput(false); + m_audio_stream->PauseOutput(System::IsPaused()); } - if (m_settings.emulation_speed != old_settings.emulation_speed) - m_system->UpdateThrottlePeriod(); + if (g_settings.emulation_speed != old_settings.emulation_speed) + System::UpdateThrottlePeriod(); - if (m_settings.cpu_execution_mode != old_settings.cpu_execution_mode) + if (g_settings.cpu_execution_mode != old_settings.cpu_execution_mode) { ReportFormattedMessage("Switching to %s CPU execution mode.", - Settings::GetCPUExecutionModeName(m_settings.cpu_execution_mode)); - m_system->SetCPUExecutionMode(m_settings.cpu_execution_mode); + Settings::GetCPUExecutionModeName(g_settings.cpu_execution_mode)); + CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); } - m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume); + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); - if (m_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale || - m_settings.gpu_fifo_size != old_settings.gpu_fifo_size || - m_settings.gpu_max_run_ahead != old_settings.gpu_max_run_ahead || - m_settings.gpu_true_color != old_settings.gpu_true_color || - m_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || - m_settings.gpu_texture_filtering != old_settings.gpu_texture_filtering || - m_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || - m_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || - m_settings.display_crop_mode != old_settings.display_crop_mode || - m_settings.display_aspect_ratio != old_settings.display_aspect_ratio) + if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale || + g_settings.gpu_fifo_size != old_settings.gpu_fifo_size || + g_settings.gpu_max_run_ahead != old_settings.gpu_max_run_ahead || + g_settings.gpu_true_color != old_settings.gpu_true_color || + g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering || + g_settings.gpu_texture_filtering != old_settings.gpu_texture_filtering || + g_settings.gpu_disable_interlacing != old_settings.gpu_disable_interlacing || + g_settings.gpu_force_ntsc_timings != old_settings.gpu_force_ntsc_timings || + g_settings.display_crop_mode != old_settings.display_crop_mode || + g_settings.display_aspect_ratio != old_settings.display_aspect_ratio) { - m_system->UpdateGPUSettings(); + g_gpu->UpdateSettings(); } - if (m_settings.cdrom_read_thread != old_settings.cdrom_read_thread) - m_system->GetCDROM()->SetUseReadThread(m_settings.cdrom_read_thread); + if (g_settings.cdrom_read_thread != old_settings.cdrom_read_thread) + g_cdrom.SetUseReadThread(g_settings.cdrom_read_thread); - if (m_settings.memory_card_types != old_settings.memory_card_types || - m_settings.memory_card_paths != old_settings.memory_card_paths) + if (g_settings.memory_card_types != old_settings.memory_card_types || + g_settings.memory_card_paths != old_settings.memory_card_paths) { - m_system->UpdateMemoryCards(); + System::UpdateMemoryCards(); } - m_system->GetDMA()->SetMaxSliceTicks(m_settings.dma_max_slice_ticks); - m_system->GetDMA()->SetHaltTicks(m_settings.dma_halt_ticks); - - if (m_settings.gpu_widescreen_hack != old_settings.gpu_widescreen_hack) - m_system->GetCPU()->GetCop2().SetWidescreenHack(m_settings.gpu_widescreen_hack); + g_dma.SetMaxSliceTicks(g_settings.dma_max_slice_ticks); + g_dma.SetHaltTicks(g_settings.dma_halt_ticks); } bool controllers_updated = false; for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - if (m_settings.controller_types[i] != old_settings.controller_types[i]) + if (g_settings.controller_types[i] != old_settings.controller_types[i]) { - if (m_system && !controllers_updated) + if (!System::IsShutdown() && !controllers_updated) { - m_system->UpdateControllers(); - m_system->ResetControllers(); + System::UpdateControllers(); + System::ResetControllers(); UpdateSoftwareCursor(); controllers_updated = true; } @@ -512,18 +514,18 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) OnControllerTypeChanged(i); } - if (m_system && !controllers_updated) + if (!System::IsShutdown() && !controllers_updated) { - m_system->UpdateControllerSettings(); + System::UpdateControllerSettings(); UpdateSoftwareCursor(); } } - if (m_display && m_settings.display_linear_filtering != old_settings.display_linear_filtering) - m_display->SetDisplayLinearFiltering(m_settings.display_linear_filtering); + if (m_display && g_settings.display_linear_filtering != old_settings.display_linear_filtering) + m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering); - if (m_display && m_settings.display_integer_scaling != old_settings.display_integer_scaling) - m_display->SetDisplayIntegerScaling(m_settings.display_integer_scaling); + if (m_display && g_settings.display_integer_scaling != old_settings.display_integer_scaling) + m_display->SetDisplayIntegerScaling(g_settings.display_integer_scaling); } void HostInterface::SetUserDirectoryToProgramDirectory() @@ -620,35 +622,35 @@ float HostInterface::GetFloatSettingValue(const char* section, const char* key, void HostInterface::ToggleSoftwareRendering() { - if (!m_system || m_settings.gpu_renderer == GPURenderer::Software) + if (System::IsShutdown() || g_settings.gpu_renderer == GPURenderer::Software) return; const GPURenderer new_renderer = - m_system->GetGPU()->IsHardwareRenderer() ? GPURenderer::Software : m_settings.gpu_renderer; + g_gpu->IsHardwareRenderer() ? GPURenderer::Software : g_settings.gpu_renderer; AddFormattedOSDMessage(2.0f, "Switching to %s renderer...", Settings::GetRendererDisplayName(new_renderer)); - m_system->RecreateGPU(new_renderer); + System::RecreateGPU(new_renderer); } void HostInterface::ModifyResolutionScale(s32 increment) { const u32 new_resolution_scale = std::clamp( - static_cast(static_cast(m_settings.gpu_resolution_scale) + increment), 1, GPU::MAX_RESOLUTION_SCALE); - if (new_resolution_scale == m_settings.gpu_resolution_scale) + static_cast(static_cast(g_settings.gpu_resolution_scale) + increment), 1, GPU::MAX_RESOLUTION_SCALE); + if (new_resolution_scale == g_settings.gpu_resolution_scale) return; - m_settings.gpu_resolution_scale = new_resolution_scale; - AddFormattedOSDMessage(2.0f, "Resolution scale set to %ux (%ux%u)", m_settings.gpu_resolution_scale, - GPU::VRAM_WIDTH * m_settings.gpu_resolution_scale, - GPU::VRAM_HEIGHT * m_settings.gpu_resolution_scale); + g_settings.gpu_resolution_scale = new_resolution_scale; + AddFormattedOSDMessage(2.0f, "Resolution scale set to %ux (%ux%u)", g_settings.gpu_resolution_scale, + GPU::VRAM_WIDTH * g_settings.gpu_resolution_scale, + GPU::VRAM_HEIGHT * g_settings.gpu_resolution_scale); - if (m_system) - m_system->GetGPU()->UpdateSettings(); + if (!System::IsShutdown()) + g_gpu->UpdateSettings(); } void HostInterface::UpdateSoftwareCursor() { - if (!m_system) + if (System::IsShutdown()) { m_display->ClearSoftwareCursor(); return; @@ -659,7 +661,7 @@ void HostInterface::UpdateSoftwareCursor() for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - Controller* controller = m_system->GetController(i); + Controller* controller = System::GetController(i); if (controller && controller->GetSoftwareCursor(&image, &image_scale)) break; } @@ -677,8 +679,10 @@ void HostInterface::UpdateSoftwareCursor() void HostInterface::RecreateSystem() { + Assert(!System::IsShutdown()); + std::unique_ptr stream = ByteStream_CreateGrowableMemoryStream(nullptr, 8 * 1024); - if (!m_system->SaveState(stream.get()) || !stream->SeekAbsolute(0)) + if (!System::SaveState(stream.get()) || !stream->SeekAbsolute(0)) { ReportError("Failed to save state before system recreation. Shutting down."); DestroySystem(); @@ -695,7 +699,7 @@ void HostInterface::RecreateSystem() return; } - m_system->ResetPerformanceCounters(); + System::ResetPerformanceCounters(); } void HostInterface::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/, diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 3476a8469..2f92313a2 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -20,13 +20,10 @@ class CDImage; class HostDisplay; class GameList; -class System; struct SystemBootParameters; class HostInterface { - friend System; - public: enum : u32 { @@ -44,12 +41,6 @@ public: /// Access to host audio stream. ALWAYS_INLINE AudioStream* GetAudioStream() const { return m_audio_stream.get(); } - /// Returns a settings object which can be modified. - ALWAYS_INLINE Settings& GetSettings() { return m_settings; } - - /// Access to emulated system. - ALWAYS_INLINE System* GetSystem() const { return m_system.get(); } - /// Initializes the emulator frontend. virtual bool Initialize(); @@ -118,6 +109,12 @@ public: /// Returns a float setting from the configuration. virtual float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f); + /// Loads the BIOS image for the specified region. + std::optional> GetBIOSImage(ConsoleRegion region); + + virtual void OnRunningGameChanged(); + virtual void OnSystemPerformanceCountersUpdated(); + protected: virtual bool AcquireHostDisplay() = 0; virtual void ReleaseHostDisplay() = 0; @@ -125,9 +122,7 @@ protected: virtual void OnSystemCreated(); virtual void OnSystemDestroyed(); - virtual void OnSystemPerformanceCountersUpdated(); virtual void OnSystemStateSaved(bool global, s32 slot); - virtual void OnRunningGameChanged(); virtual void OnControllerTypeChanged(u32 slot); /// Restores all settings to defaults. @@ -148,9 +143,6 @@ protected: /// Sets the user directory to the program directory, i.e. "portable mode". void SetUserDirectoryToProgramDirectory(); - /// Loads the BIOS image for the specified region. - std::optional> GetBIOSImage(ConsoleRegion region); - /// Quick switch between software and hardware rendering. void ToggleSoftwareRendering(); @@ -165,8 +157,8 @@ protected: std::unique_ptr m_display; std::unique_ptr m_audio_stream; - std::unique_ptr m_system; - Settings m_settings; std::string m_program_directory; std::string m_user_directory; }; + +extern HostInterface* g_host_interface; diff --git a/src/core/host_interface_progress_callback.cpp b/src/core/host_interface_progress_callback.cpp index 01b0e8e17..750b85909 100644 --- a/src/core/host_interface_progress_callback.cpp +++ b/src/core/host_interface_progress_callback.cpp @@ -2,15 +2,9 @@ #include "common/log.h" Log_SetChannel(HostInterfaceProgressCallback); -HostInterfaceProgressCallback::HostInterfaceProgressCallback(HostInterface* intf) - : BaseProgressCallback(), m_host_interface(intf) -{ -} +HostInterfaceProgressCallback::HostInterfaceProgressCallback() : BaseProgressCallback() {} -void HostInterfaceProgressCallback::PushState() -{ - BaseProgressCallback::PushState(); -} +void HostInterfaceProgressCallback::PushState() { BaseProgressCallback::PushState(); } void HostInterfaceProgressCallback::PopState() { @@ -58,38 +52,23 @@ void HostInterfaceProgressCallback::Redraw(bool force) return; m_last_progress_percent = percent; - m_host_interface->DisplayLoadingScreen(m_status_text, 0, static_cast(m_progress_range), + g_host_interface->DisplayLoadingScreen(m_status_text, 0, static_cast(m_progress_range), static_cast(m_progress_value)); } -void HostInterfaceProgressCallback::DisplayError(const char* message) -{ - Log_ErrorPrint(message); -} +void HostInterfaceProgressCallback::DisplayError(const char* message) { Log_ErrorPrint(message); } -void HostInterfaceProgressCallback::DisplayWarning(const char* message) -{ - Log_WarningPrint(message); -} +void HostInterfaceProgressCallback::DisplayWarning(const char* message) { Log_WarningPrint(message); } -void HostInterfaceProgressCallback::DisplayInformation(const char* message) -{ - Log_InfoPrint(message); -} +void HostInterfaceProgressCallback::DisplayInformation(const char* message) { Log_InfoPrint(message); } -void HostInterfaceProgressCallback::DisplayDebugMessage(const char* message) -{ - Log_DevPrint(message); -} +void HostInterfaceProgressCallback::DisplayDebugMessage(const char* message) { Log_DevPrint(message); } -void HostInterfaceProgressCallback::ModalError(const char* message) -{ - m_host_interface->ReportError(message); -} +void HostInterfaceProgressCallback::ModalError(const char* message) { g_host_interface->ReportError(message); } bool HostInterfaceProgressCallback::ModalConfirmation(const char* message) { - return m_host_interface->ConfirmMessage(message); + return g_host_interface->ConfirmMessage(message); } u32 HostInterfaceProgressCallback::ModalPrompt(const char* message, u32 num_options, ...) diff --git a/src/core/host_interface_progress_callback.h b/src/core/host_interface_progress_callback.h index 67192e692..85950d9e8 100644 --- a/src/core/host_interface_progress_callback.h +++ b/src/core/host_interface_progress_callback.h @@ -5,7 +5,7 @@ class HostInterfaceProgressCallback : public BaseProgressCallback { public: - HostInterfaceProgressCallback(HostInterface* intf); + HostInterfaceProgressCallback(); void PushState() override; void PopState() override; @@ -27,6 +27,5 @@ public: private: void Redraw(bool force); - HostInterface* m_host_interface; int m_last_progress_percent = -1; }; diff --git a/src/core/interrupt_controller.cpp b/src/core/interrupt_controller.cpp index 0c03bb95b..91747a4b0 100644 --- a/src/core/interrupt_controller.cpp +++ b/src/core/interrupt_controller.cpp @@ -4,15 +4,19 @@ #include "cpu_core.h" Log_SetChannel(InterruptController); +InterruptController g_interrupt_controller; + InterruptController::InterruptController() = default; InterruptController::~InterruptController() = default; -void InterruptController::Initialize(CPU::Core* cpu) +void InterruptController::Initialize() { - m_cpu = cpu; + Reset(); } +void InterruptController::Shutdown() {} + void InterruptController::Reset() { m_interrupt_status_register = 0; @@ -82,7 +86,7 @@ void InterruptController::UpdateCPUInterruptRequest() { // external interrupts set bit 10 only? if ((m_interrupt_status_register & m_interrupt_mask_register) != 0) - m_cpu->SetExternalInterrupt(2); + CPU::SetExternalInterrupt(2); else - m_cpu->ClearExternalInterrupt(2); + CPU::ClearExternalInterrupt(2); } diff --git a/src/core/interrupt_controller.h b/src/core/interrupt_controller.h index ab6cbd4d3..7588e882e 100644 --- a/src/core/interrupt_controller.h +++ b/src/core/interrupt_controller.h @@ -3,12 +3,7 @@ class StateWrapper; -namespace CPU -{ -class Core; -} - -class InterruptController +class InterruptController final { public: static constexpr u32 NUM_IRQS = 11; @@ -32,12 +27,13 @@ public: InterruptController(); ~InterruptController(); - void Initialize(CPU::Core* cpu); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); // Should mirror CPU state. - bool GetIRQLineState() const { return (m_interrupt_status_register != 0); } + ALWAYS_INLINE bool GetIRQLineState() const { return (m_interrupt_status_register != 0); } // Interupts are edge-triggered, so if it is masked when TriggerInterrupt() is called, it will be lost. void InterruptRequest(IRQ irq); @@ -52,9 +48,8 @@ private: void UpdateCPUInterruptRequest(); - CPU::Core* m_cpu; - u32 m_interrupt_status_register = 0; u32 m_interrupt_mask_register = DEFAULT_INTERRUPT_MASK; }; +extern InterruptController g_interrupt_controller; \ No newline at end of file diff --git a/src/core/mdec.cpp b/src/core/mdec.cpp index 85a0773c3..1d83e3435 100644 --- a/src/core/mdec.cpp +++ b/src/core/mdec.cpp @@ -1,26 +1,35 @@ #include "mdec.h" #include "common/log.h" #include "common/state_wrapper.h" +#include "cpu_core.h" #include "dma.h" #include "interrupt_controller.h" #include "system.h" #include Log_SetChannel(MDEC); +MDEC g_mdec; + MDEC::MDEC() = default; MDEC::~MDEC() = default; -void MDEC::Initialize(System* system, DMA* dma) +void MDEC::Initialize() { - m_system = system; - m_dma = dma; - m_block_copy_out_event = system->CreateTimingEvent("MDEC Block Copy Out", TICKS_PER_BLOCK, TICKS_PER_BLOCK, - std::bind(&MDEC::CopyOutBlock, this), false); + m_block_copy_out_event = TimingEvents::CreateTimingEvent("MDEC Block Copy Out", TICKS_PER_BLOCK, TICKS_PER_BLOCK, + std::bind(&MDEC::CopyOutBlock, this), false); + m_total_blocks_decoded = 0; + Reset(); +} + +void MDEC::Shutdown() +{ + m_block_copy_out_event.reset(); } void MDEC::Reset() { + m_block_copy_out_event->Deactivate(); SoftReset(); } @@ -176,12 +185,12 @@ void MDEC::UpdateStatus() // we always want data in if it's enabled const bool data_in_request = m_enable_dma_in && m_data_in_fifo.GetSpace() >= (32 * 2); m_status.data_in_request = data_in_request; - m_dma->SetRequest(DMA::Channel::MDECin, data_in_request); + g_dma.SetRequest(DMA::Channel::MDECin, data_in_request); // we only want to send data out if we have some in the fifo const bool data_out_request = m_enable_dma_out && !m_data_out_fifo.IsEmpty(); m_status.data_out_request = data_out_request; - m_dma->SetRequest(DMA::Channel::MDECout, data_out_request); + g_dma.SetRequest(DMA::Channel::MDECout, data_out_request); } u32 MDEC::ReadDataRegister() @@ -192,7 +201,7 @@ u32 MDEC::ReadDataRegister() if (HasPendingBlockCopyOut()) { Log_DevPrint("MDEC data out FIFO empty on read - stalling CPU"); - m_system->StallCPU(m_block_copy_out_event->GetTicksUntilNextExecution()); + CPU::AddPendingTicks(m_block_copy_out_event->GetTicksUntilNextExecution()); } else { @@ -695,7 +704,7 @@ void MDEC::DrawDebugStateWindow() const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(300.0f * framebuffer_scale, 350.0f * framebuffer_scale), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("MDEC State", &m_system->GetSettings().debugging.show_mdec_state)) + if (!ImGui::Begin("MDEC State", &g_settings.debugging.show_mdec_state)) { ImGui::End(); return; diff --git a/src/core/mdec.h b/src/core/mdec.h index bb25d06c3..1203dea39 100644 --- a/src/core/mdec.h +++ b/src/core/mdec.h @@ -7,9 +7,7 @@ class StateWrapper; -class System; class TimingEvent; -class DMA; class MDEC { @@ -17,7 +15,8 @@ public: MDEC(); ~MDEC(); - void Initialize(System* system, DMA* dma); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -122,9 +121,6 @@ private: const std::array& Yblk); void y_to_mono(const std::array& Yblk); - System* m_system = nullptr; - DMA* m_dma = nullptr; - StatusRegister m_status = {}; bool m_enable_dma_in = false; bool m_enable_dma_out = false; @@ -151,3 +147,5 @@ private: u32 m_total_blocks_decoded = 0; }; + +extern MDEC g_mdec; \ No newline at end of file diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index f1d405bf0..cdcb9505f 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -1,21 +1,21 @@ #include "memory_card.h" #include "common/byte_stream.h" #include "common/file_system.h" -#include "common/string_util.h" #include "common/log.h" #include "common/state_wrapper.h" +#include "common/string_util.h" #include "host_interface.h" #include "system.h" #include Log_SetChannel(MemoryCard); -MemoryCard::MemoryCard(System* system) : m_system(system) +MemoryCard::MemoryCard() { m_FLAG.no_write_yet = true; m_save_event = - system->CreateTimingEvent("Memory Card Host Flush", SAVE_DELAY_IN_SYSCLK_TICKS, SAVE_DELAY_IN_SYSCLK_TICKS, - std::bind(&MemoryCard::SaveIfChanged, this, true), false); + TimingEvents::CreateTimingEvent("Memory Card Host Flush", SAVE_DELAY_IN_SYSCLK_TICKS, SAVE_DELAY_IN_SYSCLK_TICKS, + std::bind(&MemoryCard::SaveIfChanged, this, true), false); } MemoryCard::~MemoryCard() @@ -237,16 +237,16 @@ bool MemoryCard::Transfer(const u8 data_in, u8* data_out) return ack; } -std::unique_ptr MemoryCard::Create(System* system) +std::unique_ptr MemoryCard::Create() { - std::unique_ptr mc = std::make_unique(system); + std::unique_ptr mc = std::make_unique(); mc->Format(); return mc; } -std::unique_ptr MemoryCard::Open(System* system, std::string_view filename) +std::unique_ptr MemoryCard::Open(std::string_view filename) { - std::unique_ptr mc = std::make_unique(system); + std::unique_ptr mc = std::make_unique(); mc->m_filename = filename; if (!mc->LoadFromFile()) { @@ -255,7 +255,7 @@ std::unique_ptr MemoryCard::Open(System* system, std::string_view fi message.AppendString(filename.data(), static_cast(filename.length())); message.AppendString("' could not be read, formatting."); Log_ErrorPrint(message); - system->GetHostInterface()->AddOSDMessage(message.GetCharArray(), 5.0f); + g_host_interface->AddOSDMessage(message.GetCharArray(), 5.0f); mc->Format(); } @@ -385,8 +385,7 @@ bool MemoryCard::SaveIfChanged(bool display_osd_message) Log_InfoPrintf("Saved memory card to '%s'", m_filename.c_str()); if (display_osd_message) { - m_system->GetHostInterface()->AddOSDMessage( - StringUtil::StdStringFromFormat("Saved memory card to '%s'", m_filename.c_str())); + g_host_interface->AddOSDMessage(StringUtil::StdStringFromFormat("Saved memory card to '%s'", m_filename.c_str())); } return true; diff --git a/src/core/memory_card.h b/src/core/memory_card.h index 8c826e5cd..61fd8f34d 100644 --- a/src/core/memory_card.h +++ b/src/core/memory_card.h @@ -6,7 +6,6 @@ #include #include -class System; class TimingEvent; class MemoryCard final @@ -19,11 +18,11 @@ public: NUM_SECTORS = DATA_SIZE / SECTOR_SIZE }; - MemoryCard(System* system); + MemoryCard(); ~MemoryCard(); - static std::unique_ptr Create(System* system); - static std::unique_ptr Open(System* system, std::string_view filename); + static std::unique_ptr Create(); + static std::unique_ptr Open(std::string_view filename); void Reset(); bool DoState(StateWrapper& sw); @@ -85,7 +84,6 @@ private: bool SaveIfChanged(bool display_osd_message); void QueueFileSave(); - System* m_system; std::unique_ptr m_save_event; State m_state = State::Idle; diff --git a/src/core/namco_guncon.cpp b/src/core/namco_guncon.cpp index a679a02e0..8916c5282 100644 --- a/src/core/namco_guncon.cpp +++ b/src/core/namco_guncon.cpp @@ -10,7 +10,7 @@ #include Log_SetChannel(NamcoGunCon); -NamcoGunCon::NamcoGunCon(System* system) : m_system(system) {} +NamcoGunCon::NamcoGunCon() = default; NamcoGunCon::~NamcoGunCon() = default; @@ -153,14 +153,13 @@ bool NamcoGunCon::Transfer(const u8 data_in, u8* data_out) void NamcoGunCon::UpdatePosition() { // get screen coordinates - const HostDisplay* display = m_system->GetHostInterface()->GetDisplay(); + const HostDisplay* display = g_host_interface->GetDisplay(); const s32 mouse_x = display->GetMousePositionX(); const s32 mouse_y = display->GetMousePositionY(); // are we within the active display area? u32 tick, line; - if (mouse_x < 0 || mouse_y < 0 || - !m_system->GetGPU()->ConvertScreenCoordinatesToBeamTicksAndLines(mouse_x, mouse_y, &tick, &line)) + if (mouse_x < 0 || mouse_y < 0 || !g_gpu->ConvertScreenCoordinatesToBeamTicksAndLines(mouse_x, mouse_y, &tick, &line)) { Log_DebugPrintf("Lightgun out of range for window coordinates %d,%d", mouse_x, mouse_y); m_position_x = 0x01; @@ -169,16 +168,16 @@ void NamcoGunCon::UpdatePosition() } // 8MHz units for X = 44100*768*11/7 = 53222400 / 8000000 = 6.6528 - const double divider = static_cast(m_system->GetGPU()->GetCRTCFrequency()) / 8000000.0; + const double divider = static_cast(g_gpu->GetCRTCFrequency()) / 8000000.0; m_position_x = static_cast(static_cast(tick) / static_cast(divider)); m_position_y = static_cast(line); Log_DebugPrintf("Lightgun window coordinates %d,%d -> tick %u line %u 8mhz ticks %u", mouse_x, mouse_y, tick, line, m_position_x); } -std::unique_ptr NamcoGunCon::Create(System* system) +std::unique_ptr NamcoGunCon::Create() { - return std::make_unique(system); + return std::make_unique(); } std::optional NamcoGunCon::StaticGetAxisCodeByName(std::string_view button_name) @@ -234,11 +233,11 @@ Controller::SettingList NamcoGunCon::StaticGetSettings() return SettingList(settings.begin(), settings.end()); } -void NamcoGunCon::LoadSettings(HostInterface* host_interface, const char* section) +void NamcoGunCon::LoadSettings(const char* section) { - Controller::LoadSettings(host_interface, section); + Controller::LoadSettings(section); - std::string path = host_interface->GetStringSettingValue(section, "CrosshairImagePath"); + std::string path = g_host_interface->GetStringSettingValue(section, "CrosshairImagePath"); if (path != m_crosshair_image_path) { m_crosshair_image_path = std::move(path); @@ -255,7 +254,7 @@ void NamcoGunCon::LoadSettings(HostInterface* host_interface, const char* sectio Resources::CROSSHAIR_IMAGE_DATA.data()); } - m_crosshair_image_scale = host_interface->GetFloatSettingValue(section, "CrosshairScale", 1.0f); + m_crosshair_image_scale = g_host_interface->GetFloatSettingValue(section, "CrosshairScale", 1.0f); } bool NamcoGunCon::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) diff --git a/src/core/namco_guncon.h b/src/core/namco_guncon.h index a8c70506d..274833278 100644 --- a/src/core/namco_guncon.h +++ b/src/core/namco_guncon.h @@ -15,10 +15,10 @@ public: Count }; - NamcoGunCon(System* system); + NamcoGunCon(); ~NamcoGunCon() override; - static std::unique_ptr Create(System* system); + static std::unique_ptr Create(); static std::optional StaticGetAxisCodeByName(std::string_view button_name); static std::optional StaticGetButtonCodeByName(std::string_view button_name); static AxisList StaticGetAxisNames(); @@ -32,7 +32,7 @@ public: void Reset() override; bool DoState(StateWrapper& sw) override; - void LoadSettings(HostInterface* host_interface, const char* section) override; + void LoadSettings(const char* section) override; bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) override; void SetAxisState(s32 axis_code, float value) override; @@ -58,7 +58,6 @@ private: YMSB }; - System* m_system; Common::RGBA8Image m_crosshair_image; std::string m_crosshair_image_path; float m_crosshair_image_scale = 1.0f; diff --git a/src/core/pad.cpp b/src/core/pad.cpp index 3935371c5..84f1ea15b 100644 --- a/src/core/pad.cpp +++ b/src/core/pad.cpp @@ -8,16 +8,28 @@ #include "system.h" Log_SetChannel(Pad); +Pad g_pad; + Pad::Pad() = default; Pad::~Pad() = default; -void Pad::Initialize(System* system, InterruptController* interrupt_controller) +void Pad::Initialize() { - m_system = system; - m_interrupt_controller = interrupt_controller; - m_transfer_event = system->CreateTimingEvent("Pad Serial Transfer", 1, 1, - std::bind(&Pad::TransferEvent, this, std::placeholders::_2), false); + m_transfer_event = TimingEvents::CreateTimingEvent( + "Pad Serial Transfer", 1, 1, std::bind(&Pad::TransferEvent, this, std::placeholders::_2), false); + Reset(); +} + +void Pad::Shutdown() +{ + m_transfer_event.reset(); + + for (u32 i = 0; i < NUM_SLOTS; i++) + { + m_controllers[i].reset(); + m_memory_cards[i].reset(); + } } void Pad::Reset() @@ -44,27 +56,26 @@ bool Pad::DoState(StateWrapper& sw) if (controller_type != state_controller_type) { - if (m_system->GetSettings().load_devices_from_save_states) + if (g_settings.load_devices_from_save_states) { - m_system->GetHostInterface()->AddFormattedOSDMessage( + g_host_interface->AddFormattedOSDMessage( 2.0f, "Save state contains controller type %s in port %u, but %s is used. Switching.", Settings::GetControllerTypeName(state_controller_type), i + 1u, Settings::GetControllerTypeName(controller_type)); m_controllers[i].reset(); if (state_controller_type != ControllerType::None) - m_controllers[i] = Controller::Create(m_system, state_controller_type, i); + m_controllers[i] = Controller::Create(state_controller_type, i); } else { - m_system->GetHostInterface()->AddFormattedOSDMessage(2.0f, "Ignoring mismatched controller type %s in port %u.", - Settings::GetControllerTypeName(state_controller_type), - i + 1u); + g_host_interface->AddFormattedOSDMessage(2.0f, "Ignoring mismatched controller type %s in port %u.", + Settings::GetControllerTypeName(state_controller_type), i + 1u); // we still need to read the save state controller state if (state_controller_type != ControllerType::None) { - std::unique_ptr dummy_controller = Controller::Create(m_system, state_controller_type, i); + std::unique_ptr dummy_controller = Controller::Create(state_controller_type, i); if (dummy_controller) { if (!sw.DoMarker("Controller") || !dummy_controller->DoState(sw)) @@ -85,11 +96,11 @@ bool Pad::DoState(StateWrapper& sw) bool card_present = static_cast(m_memory_cards[i]); sw.Do(&card_present); - if (sw.IsReading() && card_present && !m_system->GetSettings().load_devices_from_save_states) + if (sw.IsReading() && card_present && !g_settings.load_devices_from_save_states) { Log_WarningPrintf("Skipping loading memory card %u from save state.", i + 1u); - MemoryCard dummy_card(m_system); + MemoryCard dummy_card; if (!sw.DoMarker("MemoryCard") || !dummy_card.DoState(sw)) return false; @@ -102,13 +113,13 @@ bool Pad::DoState(StateWrapper& sw) if (card_present && !m_memory_cards[i]) { - m_system->GetHostInterface()->AddFormattedOSDMessage( + g_host_interface->AddFormattedOSDMessage( 2.0f, "Memory card %u present in save state but not in system. Creating temporary card.", i + 1u); - m_memory_cards[i] = MemoryCard::Create(m_system); + m_memory_cards[i] = MemoryCard::Create(); } else if (!card_present && m_memory_cards[i]) { - m_system->GetHostInterface()->AddFormattedOSDMessage( + g_host_interface->AddFormattedOSDMessage( 2.0f, "Memory card %u present in system but not in save state. Removing card.", i + 1u); m_memory_cards[i].reset(); } @@ -411,7 +422,7 @@ void Pad::DoACK() { Log_DebugPrintf("Triggering ACK interrupt"); m_JOY_STAT.INTR = true; - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::IRQ7); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::IRQ7); } EndTransfer(); diff --git a/src/core/pad.h b/src/core/pad.h index 300eabcff..1d16ce884 100644 --- a/src/core/pad.h +++ b/src/core/pad.h @@ -7,19 +7,18 @@ class StateWrapper; -class System; class TimingEvent; -class InterruptController; class Controller; class MemoryCard; -class Pad +class Pad final { public: Pad(); ~Pad(); - void Initialize(System* system, InterruptController* interrupt_controller); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -108,9 +107,6 @@ private: void EndTransfer(); void ResetDeviceTransferState(); - System* m_system = nullptr; - InterruptController* m_interrupt_controller = nullptr; - std::array, NUM_SLOTS> m_controllers; std::array, NUM_SLOTS> m_memory_cards; @@ -129,3 +125,5 @@ private: bool m_receive_buffer_full = false; bool m_transmit_buffer_full = false; }; + +extern Pad g_pad; \ No newline at end of file diff --git a/src/core/playstation_mouse.cpp b/src/core/playstation_mouse.cpp index 0ee49adbb..2d063b534 100644 --- a/src/core/playstation_mouse.cpp +++ b/src/core/playstation_mouse.cpp @@ -9,10 +9,10 @@ #include Log_SetChannel(PlayStationMouse); -PlayStationMouse::PlayStationMouse(System* system) : m_system(system) +PlayStationMouse::PlayStationMouse() { - m_last_host_position_x = system->GetHostInterface()->GetDisplay()->GetMousePositionX(); - m_last_host_position_y = system->GetHostInterface()->GetDisplay()->GetMousePositionY(); + m_last_host_position_x = g_host_interface->GetDisplay()->GetMousePositionX(); + m_last_host_position_y = g_host_interface->GetDisplay()->GetMousePositionY(); } PlayStationMouse::~PlayStationMouse() = default; @@ -142,7 +142,7 @@ bool PlayStationMouse::Transfer(const u8 data_in, u8* data_out) void PlayStationMouse::UpdatePosition() { // get screen coordinates - const HostDisplay* display = m_system->GetHostInterface()->GetDisplay(); + const HostDisplay* display = g_host_interface->GetDisplay(); const s32 mouse_x = display->GetMousePositionX(); const s32 mouse_y = display->GetMousePositionY(); const s32 delta_x = mouse_x - m_last_host_position_x; @@ -157,9 +157,9 @@ void PlayStationMouse::UpdatePosition() m_delta_y = static_cast(std::clamp(delta_y, std::numeric_limits::min(), std::numeric_limits::max())); } -std::unique_ptr PlayStationMouse::Create(System* system) +std::unique_ptr PlayStationMouse::Create() { - return std::make_unique(system); + return std::make_unique(); } std::optional PlayStationMouse::StaticGetAxisCodeByName(std::string_view button_name) diff --git a/src/core/playstation_mouse.h b/src/core/playstation_mouse.h index f39183dc1..1d2d3057d 100644 --- a/src/core/playstation_mouse.h +++ b/src/core/playstation_mouse.h @@ -14,10 +14,10 @@ public: Count }; - PlayStationMouse(System* system); + PlayStationMouse(); ~PlayStationMouse() override; - static std::unique_ptr Create(System* system); + static std::unique_ptr Create(); static std::optional StaticGetAxisCodeByName(std::string_view button_name); static std::optional StaticGetButtonCodeByName(std::string_view button_name); static AxisList StaticGetAxisNames(); @@ -52,8 +52,6 @@ private: DeltaY }; - System* m_system; - s32 m_last_host_position_x = 0; s32 m_last_host_position_y = 0; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index e3ea19430..ac8f209f1 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,6 +4,8 @@ #include #include +Settings g_settings; + const char* SettingInfo::StringDefaultValue() const { return default_value ? default_value : ""; diff --git a/src/core/settings.h b/src/core/settings.h index b26b24ecd..2e6b24d25 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -146,6 +146,9 @@ struct Settings bool log_to_window = false; bool log_to_file = false; + ALWAYS_INLINE bool IsUsingRecompiler() const { return (cpu_execution_mode == CPUExecutionMode::Recompiler); } + ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); } + bool HasAnyPerGameMemoryCards() const; enum : u32 @@ -216,3 +219,5 @@ struct Settings static constexpr MemoryCardType DEFAULT_MEMORY_CARD_2_TYPE = MemoryCardType::None; static constexpr LOGLEVEL DEFAULT_LOG_LEVEL = LOGLEVEL_INFO; }; + +extern Settings g_settings; diff --git a/src/core/sio.cpp b/src/core/sio.cpp index 75f5126fe..b3c7c79f0 100644 --- a/src/core/sio.cpp +++ b/src/core/sio.cpp @@ -1,23 +1,25 @@ #include "sio.h" #include "common/log.h" #include "common/state_wrapper.h" +#include "controller.h" #include "host_interface.h" #include "interrupt_controller.h" #include "memory_card.h" -#include "controller.h" -#include "system.h" Log_SetChannel(SIO); +SIO g_sio; + SIO::SIO() = default; SIO::~SIO() = default; -void SIO::Initialize(System* system, InterruptController* interrupt_controller) +void SIO::Initialize() { - m_system = system; - m_interrupt_controller = interrupt_controller; + Reset(); } +void SIO::Shutdown() {} + void SIO::Reset() { SoftReset(); diff --git a/src/core/sio.h b/src/core/sio.h index 008e8f016..cc49b0891 100644 --- a/src/core/sio.h +++ b/src/core/sio.h @@ -7,8 +7,6 @@ class StateWrapper; -class System; -class InterruptController; class Controller; class MemoryCard; @@ -18,7 +16,8 @@ public: SIO(); ~SIO(); - void Initialize(System* system, InterruptController* interrupt_controller); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -73,11 +72,10 @@ private: void SoftReset(); - System* m_system = nullptr; - InterruptController* m_interrupt_controller = nullptr; - SIO_CTRL m_SIO_CTRL = {}; SIO_STAT m_SIO_STAT = {}; SIO_MODE m_SIO_MODE = {}; u16 m_SIO_BAUD = 0; }; + +extern SIO g_sio; \ No newline at end of file diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 85fc37d0b..e684154ce 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -11,21 +11,28 @@ #include Log_SetChannel(SPU); +SPU g_spu; + SPU::SPU() = default; SPU::~SPU() = default; -void SPU::Initialize(System* system, DMA* dma, CDROM* cdrom, InterruptController* interrupt_controller) +void SPU::Initialize() { - m_system = system; - m_dma = dma; - m_cdrom = cdrom; - m_interrupt_controller = interrupt_controller; - m_tick_event = m_system->CreateTimingEvent("SPU Sample", SYSCLK_TICKS_PER_SPU_TICK, SYSCLK_TICKS_PER_SPU_TICK, - std::bind(&SPU::Execute, this, std::placeholders::_1), false); + m_tick_event = TimingEvents::CreateTimingEvent("SPU Sample", SYSCLK_TICKS_PER_SPU_TICK, SYSCLK_TICKS_PER_SPU_TICK, + std::bind(&SPU::Execute, this, std::placeholders::_1), false); m_transfer_event = - m_system->CreateTimingEvent("SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, - std::bind(&SPU::ExecuteTransfer, this, std::placeholders::_1), false); + TimingEvents::CreateTimingEvent("SPU Transfer", TRANSFER_TICKS_PER_HALFWORD, TRANSFER_TICKS_PER_HALFWORD, + std::bind(&SPU::ExecuteTransfer, this, std::placeholders::_1), false); + + Reset(); +} + +void SPU::Shutdown() +{ + m_tick_event.reset(); + m_transfer_event.reset(); + m_dump_writer.reset(); } void SPU::Reset() @@ -81,6 +88,7 @@ void SPU::Reset() } m_transfer_fifo.Clear(); + m_transfer_event->Deactivate(); m_ram.fill(0); UpdateEventInterval(); } @@ -147,7 +155,7 @@ bool SPU::DoState(StateWrapper& sw) if (sw.IsReading()) { - m_system->GetHostInterface()->GetAudioStream()->EmptyBuffers(); + g_host_interface->GetAudioStream()->EmptyBuffers(); UpdateEventInterval(); UpdateTransferEvent(); } @@ -649,7 +657,7 @@ void SPU::CheckRAMIRQ(u32 address) { Log_DebugPrintf("SPU IRQ at address 0x%08X", address); m_SPUSTAT.irq9_flag = true; - m_interrupt_controller->InterruptRequest(InterruptController::IRQ::SPU); + g_interrupt_controller.InterruptRequest(InterruptController::IRQ::SPU); } } @@ -675,7 +683,7 @@ void SPU::Execute(TickCount ticks) while (remaining_frames > 0) { - AudioStream* const output_stream = m_system->GetHostInterface()->GetAudioStream(); + AudioStream* const output_stream = g_host_interface->GetAudioStream(); s16* output_frame_start; u32 output_frame_space = remaining_frames; output_stream->BeginWrite(&output_frame_start, &output_frame_space); @@ -730,7 +738,7 @@ void SPU::Execute(TickCount ticks) UpdateNoise(); // Mix in CD audio. - const auto [cd_audio_left, cd_audio_right] = m_cdrom->GetAudioFrame(); + const auto [cd_audio_left, cd_audio_right] = g_cdrom.GetAudioFrame(); if (m_SPUCNT.cd_audio_enable) { const s32 cd_audio_volume_left = ApplyVolume(s32(cd_audio_left), m_cd_audio_volume_left); @@ -782,7 +790,7 @@ void SPU::UpdateEventInterval() // Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when // we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with // the SPU state. - const u32 max_slice_frames = m_system->GetHostInterface()->GetAudioStream()->GetBufferSize(); + const u32 max_slice_frames = g_host_interface->GetAudioStream()->GetBufferSize(); // TODO: Make this predict how long until the interrupt will be hit instead... const u32 interval = (m_SPUCNT.enable && m_SPUCNT.irq9_enable) ? 1 : max_slice_frames; @@ -930,7 +938,7 @@ void SPU::UpdateDMARequest() } // This might call us back directly. - m_dma->SetRequest(DMA::Channel::SPU, m_SPUSTAT.dma_request); + g_dma.SetRequest(DMA::Channel::SPU, m_SPUSTAT.dma_request); } void SPU::DMARead(u32* words, u32 word_count) @@ -1744,7 +1752,7 @@ void SPU::DrawDebugStateWindow() const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 800.0f * framebuffer_scale), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("SPU State", &m_system->GetSettings().debugging.show_spu_state)) + if (!ImGui::Begin("SPU State", &g_settings.debugging.show_spu_state)) { ImGui::End(); return; diff --git a/src/core/spu.h b/src/core/spu.h index dc10edd17..88f6fb613 100644 --- a/src/core/spu.h +++ b/src/core/spu.h @@ -11,11 +11,7 @@ namespace Common { class WAVWriter; } -class System; class TimingEvent; -class DMA; -class CDROM; -class InterruptController; class SPU { @@ -23,7 +19,8 @@ public: SPU(); ~SPU(); - void Initialize(System* system, DMA* dma, CDROM* cdrom, InterruptController* interrupt_controller); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -369,10 +366,6 @@ private: void UpdateTransferEvent(); void UpdateDMARequest(); - System* m_system = nullptr; - DMA* m_dma = nullptr; - CDROM* m_cdrom = nullptr; - InterruptController* m_interrupt_controller = nullptr; std::unique_ptr m_tick_event; std::unique_ptr m_transfer_event; std::unique_ptr m_dump_writer; @@ -421,4 +414,6 @@ private: InlineFIFOQueue m_transfer_fifo; std::array m_ram{}; -}; \ No newline at end of file +}; + +extern SPU g_spu; \ No newline at end of file diff --git a/src/core/system.cpp b/src/core/system.cpp index e08fc875f..38d389a2d 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -12,6 +12,7 @@ #include "dma.h" #include "game_list.h" #include "gpu.h" +#include "gte.h" #include "host_display.h" #include "host_interface.h" #include "host_interface_progress_callback.h" @@ -48,30 +49,160 @@ SystemBootParameters::SystemBootParameters(const SystemBootParameters& copy) SystemBootParameters::~SystemBootParameters() = default; -System::System(HostInterface* host_interface) : m_host_interface(host_interface) +namespace System { + +static bool LoadEXE(const char* filename, std::vector& bios_image); +static bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image); +static bool LoadPSF(const char* filename, std::vector& bios_image); +static bool SetExpansionROM(const char* filename); + +/// Opens CD image, preloading if needed. +static std::unique_ptr OpenCDImage(const char* path, bool force_preload); + +static bool DoLoadState(ByteStream* stream, bool force_software_renderer); +static bool DoState(StateWrapper& sw); +static bool CreateGPU(GPURenderer renderer); + +static bool Initialize(bool force_software_renderer); + +static void UpdateRunningGame(const char* path, CDImage* image); + +static State s_state = State::Shutdown; + +static ConsoleRegion s_region = ConsoleRegion::NTSC_U; +static u32 s_frame_number = 1; +static u32 s_internal_frame_number = 1; + +static std::string s_running_game_path; +static std::string s_running_game_code; +static std::string s_running_game_title; + +static float s_throttle_frequency = 60.0f; +static s32 s_throttle_period = 0; +static u64 s_last_throttle_time = 0; +static Common::Timer s_throttle_timer; +static Common::Timer s_speed_lost_time_timestamp; + +static float s_average_frame_time_accumulator = 0.0f; +static float s_worst_frame_time_accumulator = 0.0f; + +static float s_vps = 0.0f; +static float s_fps = 0.0f; +static float s_speed = 0.0f; +static float s_worst_frame_time = 0.0f; +static float s_average_frame_time = 0.0f; +static u32 s_last_frame_number = 0; +static u32 s_last_internal_frame_number = 0; +static u32 s_last_global_tick_counter = 0; +static Common::Timer s_fps_timer; +static Common::Timer s_frame_timer; + +// Playlist of disc images. +std::vector m_media_playlist; + +State GetState() { - m_cpu = std::make_unique(); - m_cpu_code_cache = std::make_unique(); - m_bus = std::make_unique(); - m_dma = std::make_unique(); - m_interrupt_controller = std::make_unique(); - m_cdrom = std::make_unique(); - m_pad = std::make_unique(); - m_timers = std::make_unique(); - m_spu = std::make_unique(); - m_mdec = std::make_unique(); - m_sio = std::make_unique(); - m_region = host_interface->m_settings.region; - m_cpu_execution_mode = host_interface->m_settings.cpu_execution_mode; + return s_state; } -System::~System() +void SetState(State new_state) { - // we have to explicitly destroy components because they can deregister events - DestroyComponents(); + Assert(s_state == State::Paused || s_state == State::Running); + Assert(new_state == State::Paused || new_state == State::Running); + s_state = new_state; } -ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) +bool IsRunning() +{ + return s_state == State::Running; +} + +bool IsPaused() +{ + return s_state == State::Paused; +} + +bool IsShutdown() +{ + return s_state == State::Shutdown; +} + +bool IsValid() +{ + return s_state != State::Shutdown; +} + +ConsoleRegion GetRegion() +{ + return s_region; +} + +bool IsPALRegion() +{ + return s_region == ConsoleRegion::PAL; +} + +u32 GetFrameNumber() +{ + return s_frame_number; +} + +u32 GetInternalFrameNumber() +{ + return s_internal_frame_number; +} + +void FrameDone() +{ + s_frame_number++; + CPU::g_state.frame_done = true; +} + +void IncrementInternalFrameNumber() +{ + s_internal_frame_number++; +} + +const std::string& GetRunningPath() +{ + return s_running_game_path; +} +const std::string& GetRunningCode() +{ + return s_running_game_code; +} + +const std::string& GetRunningTitle() +{ + return s_running_game_title; +} + +float GetFPS() +{ + return s_fps; +} +float GetVPS() +{ + return s_vps; +} +float GetEmulationSpeed() +{ + return s_speed; +} +float GetAverageFrameTime() +{ + return s_average_frame_time; +} +float GetWorstFrameTime() +{ + return s_worst_frame_time; +} +float GetThrottleFrequency() +{ + return s_throttle_frequency; +} + +ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region) { switch (region) { @@ -88,22 +219,17 @@ ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) } } -std::unique_ptr System::Create(HostInterface* host_interface) -{ - return std::unique_ptr(new System(host_interface)); -} - -bool System::RecreateGPU(GPURenderer renderer) +bool RecreateGPU(GPURenderer renderer) { // save current state std::unique_ptr state_stream = ByteStream_CreateGrowableMemoryStream(); StateWrapper sw(state_stream.get(), StateWrapper::Mode::Write); - const bool state_valid = m_gpu->DoState(sw) && DoEventsState(sw); + const bool state_valid = g_gpu->DoState(sw) && TimingEvents::DoState(sw, TimingEvents::GetGlobalTickCounter()); if (!state_valid) Log_ErrorPrintf("Failed to save old GPU state when switching renderers"); // create new renderer - m_gpu.reset(); + g_gpu.reset(); if (!CreateGPU(renderer)) { Panic("Failed to recreate GPU"); @@ -114,34 +240,22 @@ bool System::RecreateGPU(GPURenderer renderer) { state_stream->SeekAbsolute(0); sw.SetMode(StateWrapper::Mode::Read); - m_gpu->DoState(sw); - DoEventsState(sw); + g_gpu->DoState(sw); + TimingEvents::DoState(sw, TimingEvents::GetGlobalTickCounter()); } return true; } -void System::UpdateGPUSettings() -{ - m_gpu->UpdateSettings(); -} - -void System::SetCPUExecutionMode(CPUExecutionMode mode) -{ - m_cpu_execution_mode = mode; - m_cpu_code_cache->Flush(); - m_cpu_code_cache->SetUseRecompiler(mode == CPUExecutionMode::Recompiler); -} - -std::unique_ptr System::OpenCDImage(const char* path, bool force_preload) +std::unique_ptr OpenCDImage(const char* path, bool force_preload) { std::unique_ptr media = CDImage::Open(path); if (!media) return {}; - if (force_preload || GetSettings().cdrom_load_image_to_ram) + if (force_preload || g_settings.cdrom_load_image_to_ram) { - HostInterfaceProgressCallback callback(m_host_interface); + HostInterfaceProgressCallback callback; std::unique_ptr memory_image = CDImage::CreateMemoryImage(media.get(), &callback); if (memory_image) media = std::move(memory_image); @@ -152,10 +266,15 @@ std::unique_ptr System::OpenCDImage(const char* path, bool force_preloa return media; } -bool System::Boot(const SystemBootParameters& params) +bool Boot(const SystemBootParameters& params) { + Assert(s_state == State::Shutdown); + Assert(m_media_playlist.empty()); + s_state = State::Starting; + s_region = g_settings.region; + if (params.state_stream) - return DoLoadState(params.state_stream.get(), true, params.force_software_renderer); + return DoLoadState(params.state_stream.get(), params.force_software_renderer); // Load CD image up and detect region. std::unique_ptr media; @@ -168,10 +287,10 @@ bool System::Boot(const SystemBootParameters& params) if (exe_boot || psf_boot) { // TODO: Pull region from PSF - if (m_region == ConsoleRegion::Auto) + if (s_region == ConsoleRegion::Auto) { Log_InfoPrintf("Defaulting to NTSC-U region for executable."); - m_region = ConsoleRegion::NTSC_U; + s_region = ConsoleRegion::NTSC_U; } } else @@ -182,7 +301,8 @@ bool System::Boot(const SystemBootParameters& params) m_media_playlist = GameList::ParseM3UFile(params.filename.c_str()); if (m_media_playlist.empty()) { - m_host_interface->ReportFormattedError("Failed to parse playlist '%s'", params.filename.c_str()); + g_host_interface->ReportFormattedError("Failed to parse playlist '%s'", params.filename.c_str()); + Shutdown(); return false; } @@ -207,25 +327,26 @@ bool System::Boot(const SystemBootParameters& params) media = OpenCDImage(media_path.c_str(), params.load_image_to_ram); if (!media) { - m_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str()); + g_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str()); + Shutdown(); return false; } - if (m_region == ConsoleRegion::Auto) + if (s_region == ConsoleRegion::Auto) { const DiscRegion disc_region = GameList::GetRegionForImage(media.get()); if (disc_region != DiscRegion::Other) { - m_region = GetConsoleRegionForDiscRegion(disc_region); + s_region = GetConsoleRegionForDiscRegion(disc_region); Log_InfoPrintf("Auto-detected console %s region for '%s' (region %s)", - Settings::GetConsoleRegionName(m_region), params.filename.c_str(), + Settings::GetConsoleRegionName(s_region), params.filename.c_str(), Settings::GetDiscRegionName(disc_region)); } else { - m_region = ConsoleRegion::NTSC_U; + s_region = ConsoleRegion::NTSC_U; Log_WarningPrintf("Could not determine console region for disc region %s. Defaulting to %s.", - Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(m_region)); + Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(s_region)); } } } @@ -233,21 +354,25 @@ bool System::Boot(const SystemBootParameters& params) else { // Default to NTSC for BIOS boot. - if (m_region == ConsoleRegion::Auto) - m_region = ConsoleRegion::NTSC_U; + if (s_region == ConsoleRegion::Auto) + s_region = ConsoleRegion::NTSC_U; } // Load BIOS image. - std::optional bios_image = m_host_interface->GetBIOSImage(m_region); + std::optional bios_image = g_host_interface->GetBIOSImage(s_region); if (!bios_image) { - m_host_interface->ReportFormattedError("Failed to load %s BIOS", Settings::GetConsoleRegionName(m_region)); + g_host_interface->ReportFormattedError("Failed to load %s BIOS", Settings::GetConsoleRegionName(s_region)); + Shutdown(); return false; } // Component setup. - if (!InitializeComponents(params.force_software_renderer)) + if (!Initialize(params.force_software_renderer)) + { + Shutdown(); return false; + } // Notify change of disc. UpdateRunningGame(params.filename.c_str(), media.get()); @@ -257,203 +382,236 @@ bool System::Boot(const SystemBootParameters& params) // Enable tty by patching bios. const BIOS::Hash bios_hash = BIOS::GetHash(*bios_image); - if (GetSettings().bios_patch_tty_enable) + if (g_settings.bios_patch_tty_enable) BIOS::PatchBIOSEnableTTY(*bios_image, bios_hash); // Load EXE late after BIOS. if (exe_boot && !LoadEXE(params.filename.c_str(), *bios_image)) { - m_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str()); + g_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str()); + Shutdown(); return false; } else if (psf_boot && !LoadPSF(params.filename.c_str(), *bios_image)) { - m_host_interface->ReportFormattedError("Failed to load PSF file '%s'", params.filename.c_str()); + g_host_interface->ReportFormattedError("Failed to load PSF file '%s'", params.filename.c_str()); + Shutdown(); return false; } // Insert CD, and apply fastboot patch if enabled. if (media) - m_cdrom->InsertMedia(std::move(media)); - if (m_cdrom->HasMedia() && - (params.override_fast_boot.has_value() ? params.override_fast_boot.value() : GetSettings().bios_patch_fast_boot)) + g_cdrom.InsertMedia(std::move(media)); + if (g_cdrom.HasMedia() && + (params.override_fast_boot.has_value() ? params.override_fast_boot.value() : g_settings.bios_patch_fast_boot)) { BIOS::PatchBIOSFastBoot(*bios_image, bios_hash); } // Load the patched BIOS up. - m_bus->SetBIOS(*bios_image); + Bus::SetBIOS(*bios_image); // Good to go. + s_state = State::Running; return true; } -bool System::InitializeComponents(bool force_software_renderer) +bool Initialize(bool force_software_renderer) { - const Settings& settings = GetSettings(); - if (!CreateGPU(force_software_renderer ? GPURenderer::Software : settings.gpu_renderer)) + s_frame_number = 1; + s_internal_frame_number = 1; + + s_throttle_frequency = 60.0f; + s_throttle_period = 0; + s_last_throttle_time = 0; + s_throttle_timer.Reset(); + s_speed_lost_time_timestamp.Reset(); + + s_average_frame_time_accumulator = 0.0f; + s_worst_frame_time_accumulator = 0.0f; + + s_vps = 0.0f; + s_fps = 0.0f; + s_speed = 0.0f; + s_worst_frame_time = 0.0f; + s_average_frame_time = 0.0f; + s_last_frame_number = 0; + s_last_internal_frame_number = 0; + s_last_global_tick_counter = 0; + s_fps_timer.Reset(); + s_frame_timer.Reset(); + + TimingEvents::Initialize(); + + CPU::Initialize(); + CPU::CodeCache::Initialize(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); + Bus::Initialize(); + + if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer)) return false; - m_cpu->Initialize(m_bus.get()); - m_cpu_code_cache->Initialize(this, m_cpu.get(), m_bus.get(), m_cpu_execution_mode == CPUExecutionMode::Recompiler); - m_bus->Initialize(m_cpu.get(), m_cpu_code_cache.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), - m_cdrom.get(), m_pad.get(), m_timers.get(), m_spu.get(), m_mdec.get(), m_sio.get()); + g_dma.Initialize(); - m_dma->Initialize(this, m_bus.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(), m_spu.get(), - m_mdec.get()); + g_interrupt_controller.Initialize(); - m_interrupt_controller->Initialize(m_cpu.get()); - - m_cdrom->Initialize(this, m_dma.get(), m_interrupt_controller.get(), m_spu.get()); - m_pad->Initialize(this, m_interrupt_controller.get()); - m_timers->Initialize(this, m_interrupt_controller.get(), m_gpu.get()); - m_spu->Initialize(this, m_dma.get(), m_cdrom.get(), m_interrupt_controller.get()); - m_mdec->Initialize(this, m_dma.get()); - - // load settings - m_cpu->GetCop2().SetWidescreenHack(settings.gpu_widescreen_hack); + g_cdrom.Initialize(); + g_pad.Initialize(); + g_timers.Initialize(); + g_spu.Initialize(); + g_mdec.Initialize(); + g_sio.Initialize(); UpdateThrottlePeriod(); return true; } -void System::DestroyComponents() +void Shutdown() { - m_mdec.reset(); - m_spu.reset(); - m_timers.reset(); - m_pad.reset(); - m_cdrom.reset(); - m_gpu.reset(); - m_interrupt_controller.reset(); - m_dma.reset(); - m_cpu_code_cache.reset(); - m_bus.reset(); - m_cpu.reset(); + if (s_state == State::Shutdown) + return; + + g_sio.Shutdown(); + g_mdec.Shutdown(); + g_spu.Shutdown(); + g_timers.Shutdown(); + g_pad.Shutdown(); + g_cdrom.Shutdown(); + g_gpu.reset(); + g_interrupt_controller.Shutdown(); + g_dma.Shutdown(); + CPU::CodeCache::Shutdown(); + Bus::Shutdown(); + CPU::Shutdown(); + TimingEvents::Shutdown(); + s_running_game_code.clear(); + s_running_game_path.clear(); + s_running_game_title.clear(); + m_media_playlist.clear(); + s_state = State::Shutdown; } -bool System::CreateGPU(GPURenderer renderer) +bool CreateGPU(GPURenderer renderer) { switch (renderer) { case GPURenderer::HardwareOpenGL: - m_gpu = GPU::CreateHardwareOpenGLRenderer(); + g_gpu = GPU::CreateHardwareOpenGLRenderer(); break; case GPURenderer::HardwareVulkan: - m_gpu = GPU::CreateHardwareVulkanRenderer(); + g_gpu = GPU::CreateHardwareVulkanRenderer(); break; #ifdef WIN32 case GPURenderer::HardwareD3D11: - m_gpu = GPU::CreateHardwareD3D11Renderer(); + g_gpu = GPU::CreateHardwareD3D11Renderer(); break; #endif case GPURenderer::Software: default: - m_gpu = GPU::CreateSoftwareRenderer(); + g_gpu = GPU::CreateSoftwareRenderer(); break; } - if (!m_gpu || !m_gpu->Initialize(m_host_interface->GetDisplay(), this, m_dma.get(), m_interrupt_controller.get(), - m_timers.get())) + if (!g_gpu || !g_gpu->Initialize(g_host_interface->GetDisplay())) { Log_ErrorPrintf("Failed to initialize GPU, falling back to software"); - m_gpu.reset(); - m_gpu = GPU::CreateSoftwareRenderer(); - if (!m_gpu->Initialize(m_host_interface->GetDisplay(), this, m_dma.get(), m_interrupt_controller.get(), - m_timers.get())) - { + g_gpu.reset(); + g_gpu = GPU::CreateSoftwareRenderer(); + if (!g_gpu->Initialize(g_host_interface->GetDisplay())) return false; - } } - m_bus->SetGPU(m_gpu.get()); - m_dma->SetGPU(m_gpu.get()); - m_timers->SetGPU(m_gpu.get()); + // we put this here rather than in Initialize() because of the virtual calls + g_gpu->Reset(); return true; } -bool System::DoState(StateWrapper& sw) +bool DoState(StateWrapper& sw) { if (!sw.DoMarker("System")) return false; - sw.Do(&m_region); - sw.Do(&m_frame_number); - sw.Do(&m_internal_frame_number); - sw.Do(&m_global_tick_counter); + // TODO: Move this into timing state + u32 global_tick_counter = TimingEvents::GetGlobalTickCounter(); - if (!sw.DoMarker("CPU") || !m_cpu->DoState(sw)) + sw.Do(&s_region); + sw.Do(&s_frame_number); + sw.Do(&s_internal_frame_number); + sw.Do(&global_tick_counter); + + if (!sw.DoMarker("CPU") || !CPU::DoState(sw)) return false; if (sw.IsReading()) - m_cpu_code_cache->Flush(); + CPU::CodeCache::Flush(); - if (!sw.DoMarker("Bus") || !m_bus->DoState(sw)) + if (!sw.DoMarker("Bus") || !Bus::DoState(sw)) return false; - if (!sw.DoMarker("DMA") || !m_dma->DoState(sw)) + if (!sw.DoMarker("DMA") || !g_dma.DoState(sw)) return false; - if (!sw.DoMarker("InterruptController") || !m_interrupt_controller->DoState(sw)) + if (!sw.DoMarker("InterruptController") || !g_interrupt_controller.DoState(sw)) return false; - if (!sw.DoMarker("GPU") || !m_gpu->DoState(sw)) + if (!sw.DoMarker("GPU") || !g_gpu->DoState(sw)) return false; - if (!sw.DoMarker("CDROM") || !m_cdrom->DoState(sw)) + if (!sw.DoMarker("CDROM") || !g_cdrom.DoState(sw)) return false; - if (!sw.DoMarker("Pad") || !m_pad->DoState(sw)) + if (!sw.DoMarker("Pad") || !g_pad.DoState(sw)) return false; - if (!sw.DoMarker("Timers") || !m_timers->DoState(sw)) + if (!sw.DoMarker("Timers") || !g_timers.DoState(sw)) return false; - if (!sw.DoMarker("SPU") || !m_spu->DoState(sw)) + if (!sw.DoMarker("SPU") || !g_spu.DoState(sw)) return false; - if (!sw.DoMarker("MDEC") || !m_mdec->DoState(sw)) + if (!sw.DoMarker("MDEC") || !g_mdec.DoState(sw)) return false; - if (!sw.DoMarker("SIO") || !m_sio->DoState(sw)) + if (!sw.DoMarker("SIO") || !g_sio.DoState(sw)) return false; - if (!sw.DoMarker("Events") || !DoEventsState(sw)) + if (!sw.DoMarker("Events") || !TimingEvents::DoState(sw, global_tick_counter)) return false; return !sw.HasError(); } -void System::Reset() +void Reset() { - m_cpu->Reset(); - m_cpu_code_cache->Flush(); - m_bus->Reset(); - m_dma->Reset(); - m_interrupt_controller->Reset(); - m_gpu->Reset(); - m_cdrom->Reset(); - m_pad->Reset(); - m_timers->Reset(); - m_spu->Reset(); - m_mdec->Reset(); - m_sio->Reset(); - m_frame_number = 1; - m_internal_frame_number = 0; - m_global_tick_counter = 0; - m_last_event_run_time = 0; + CPU::Reset(); + CPU::CodeCache::Flush(); + Bus::Reset(); + g_dma.Reset(); + g_interrupt_controller.Reset(); + g_gpu->Reset(); + g_cdrom.Reset(); + g_pad.Reset(); + g_timers.Reset(); + g_spu.Reset(); + g_mdec.Reset(); + g_sio.Reset(); + s_frame_number = 1; + s_internal_frame_number = 0; + TimingEvents::Reset(); ResetPerformanceCounters(); } -bool System::LoadState(ByteStream* state) +bool LoadState(ByteStream* state) { - return DoLoadState(state, false, false); + if (IsShutdown()) + return false; + + return DoLoadState(state, false); } -bool System::DoLoadState(ByteStream* state, bool init_components, bool force_software_renderer) +bool DoLoadState(ByteStream* state, bool force_software_renderer) { SAVE_STATE_HEADER header; if (!state->Read2(&header, sizeof(header))) @@ -464,7 +622,7 @@ bool System::DoLoadState(ByteStream* state, bool init_components, bool force_sof if (header.version != SAVE_STATE_VERSION) { - m_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.", + g_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.", SAVE_STATE_VERSION, header.version); return false; } @@ -480,13 +638,13 @@ bool System::DoLoadState(ByteStream* state, bool init_components, bool force_sof return false; } - media = m_cdrom->RemoveMedia(); + media = g_cdrom.RemoveMedia(); if (!media || media->GetFileName() != media_filename) { media = OpenCDImage(media_filename.c_str(), false); if (!media) { - m_host_interface->ReportFormattedError("Failed to open CD image from save state: '%s'.", + g_host_interface->ReportFormattedError("Failed to open CD image from save state: '%s'.", media_filename.c_str()); return false; } @@ -495,33 +653,33 @@ bool System::DoLoadState(ByteStream* state, bool init_components, bool force_sof UpdateRunningGame(media_filename.c_str(), media.get()); - if (init_components) + if (s_state == State::Starting) { - if (!InitializeComponents(force_software_renderer)) + if (!Initialize(force_software_renderer)) return false; UpdateControllers(); UpdateMemoryCards(); if (media) - m_cdrom->InsertMedia(std::move(media)); + g_cdrom.InsertMedia(std::move(media)); } else { - m_cdrom->Reset(); + g_cdrom.Reset(); if (media) - m_cdrom->InsertMedia(std::move(media)); + g_cdrom.InsertMedia(std::move(media)); else - m_cdrom->RemoveMedia(); + g_cdrom.RemoveMedia(); // ensure the correct card is loaded - if (GetSettings().HasAnyPerGameMemoryCards()) + if (g_settings.HasAnyPerGameMemoryCards()) UpdateMemoryCards(); } if (header.data_compression_type != 0) { - m_host_interface->ReportFormattedError("Unknown save state compression type %u", header.data_compression_type); + g_host_interface->ReportFormattedError("Unknown save state compression type %u", header.data_compression_type); return false; } @@ -529,11 +687,25 @@ bool System::DoLoadState(ByteStream* state, bool init_components, bool force_sof return false; StateWrapper sw(state, StateWrapper::Mode::Read); - return DoState(sw); + if (!DoState(sw)) + { + if (s_state == State::Starting) + Shutdown(); + + return false; + } + + if (s_state == State::Starting) + s_state = State::Running; + + return true; } -bool System::SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) +bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) { + if (IsShutdown()) + return false; + SAVE_STATE_HEADER header = {}; const u64 header_position = state->GetPosition(); @@ -543,12 +715,12 @@ bool System::SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) // fill in header header.magic = SAVE_STATE_MAGIC; header.version = SAVE_STATE_VERSION; - StringUtil::Strlcpy(header.title, m_running_game_title.c_str(), sizeof(header.title)); - StringUtil::Strlcpy(header.game_code, m_running_game_code.c_str(), sizeof(header.game_code)); + StringUtil::Strlcpy(header.title, s_running_game_title.c_str(), sizeof(header.title)); + StringUtil::Strlcpy(header.game_code, s_running_game_code.c_str(), sizeof(header.game_code)); - if (m_cdrom->HasMedia()) + if (g_cdrom.HasMedia()) { - const std::string& media_filename = m_cdrom->GetMediaFileName(); + const std::string& media_filename = g_cdrom.GetMediaFileName(); header.offset_to_media_filename = static_cast(state->GetPosition()); header.media_filename_length = static_cast(media_filename.length()); if (!media_filename.empty() && !state->Write2(media_filename.data(), header.media_filename_length)) @@ -559,10 +731,10 @@ bool System::SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) if (screenshot_size > 0) { std::vector screenshot_buffer; - m_gpu->ResetGraphicsAPIState(); + g_gpu->ResetGraphicsAPIState(); const bool screenshot_saved = - m_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_size, screenshot_size); - m_gpu->RestoreGraphicsAPIState(); + g_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_size, screenshot_size); + g_gpu->RestoreGraphicsAPIState(); if (screenshot_saved && !screenshot_buffer.empty()) { header.offset_to_screenshot = static_cast(state->GetPosition()); @@ -597,48 +769,32 @@ bool System::SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) return true; } -void System::RunFrame() +void RunFrame() { - m_frame_timer.Reset(); - m_frame_done = false; + s_frame_timer.Reset(); - // Duplicated to avoid branch in the while loop, as the downcount can be quite low at times. - if (m_cpu_execution_mode == CPUExecutionMode::Interpreter) - { - do - { - UpdateCPUDowncount(); - m_cpu->Execute(); - RunEvents(); - } while (!m_frame_done); - } + if (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter) + CPU::Execute(); else - { - do - { - UpdateCPUDowncount(); - m_cpu_code_cache->Execute(); - RunEvents(); - } while (!m_frame_done); - } + CPU::CodeCache::Execute(); // Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns. - m_spu->GeneratePendingSamples(); + g_spu.GeneratePendingSamples(); } -void System::SetThrottleFrequency(float frequency) +void SetThrottleFrequency(float frequency) { - m_throttle_frequency = frequency; + s_throttle_frequency = frequency; UpdateThrottlePeriod(); } -void System::UpdateThrottlePeriod() +void UpdateThrottlePeriod() { - m_throttle_period = static_cast(1000000000.0 / static_cast(m_throttle_frequency) / - static_cast(GetSettings().emulation_speed)); + s_throttle_period = static_cast(1000000000.0 / static_cast(s_throttle_frequency) / + static_cast(g_settings.emulation_speed)); } -void System::Throttle() +void Throttle() { // Allow variance of up to 40ms either way. constexpr s64 MAX_VARIANCE_TIME = INT64_C(40000000); @@ -647,24 +803,24 @@ void System::Throttle() constexpr s64 MINIMUM_SLEEP_TIME = INT64_C(1000000); // Use unsigned for defined overflow/wrap-around. - const u64 time = static_cast(m_throttle_timer.GetTimeNanoseconds()); - const s64 sleep_time = static_cast(m_last_throttle_time - time); + const u64 time = static_cast(s_throttle_timer.GetTimeNanoseconds()); + const s64 sleep_time = static_cast(s_last_throttle_time - time); if (sleep_time < -MAX_VARIANCE_TIME) { #ifndef _DEBUG // Don't display the slow messages in debug, it'll always be slow... // Limit how often the messages are displayed. - if (m_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f) + if (s_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f) { Log_WarningPrintf("System too slow, lost %.2f ms", static_cast(-sleep_time - MAX_VARIANCE_TIME) / 1000000.0); - m_speed_lost_time_timestamp.Reset(); + s_speed_lost_time_timestamp.Reset(); } #endif - m_last_throttle_time = 0; - m_throttle_timer.Reset(); + s_last_throttle_time = 0; + s_throttle_timer.Reset(); } - else if (sleep_time >= MINIMUM_SLEEP_TIME && sleep_time <= m_throttle_period) + else if (sleep_time >= MINIMUM_SLEEP_TIME && sleep_time <= s_throttle_period) { #ifdef WIN32 Sleep(static_cast(sleep_time / 1000000)); @@ -674,52 +830,53 @@ void System::Throttle() #endif } - m_last_throttle_time += m_throttle_period; + s_last_throttle_time += s_throttle_period; } -void System::UpdatePerformanceCounters() +void UpdatePerformanceCounters() { - const float frame_time = static_cast(m_frame_timer.GetTimeMilliseconds()); - m_average_frame_time_accumulator += frame_time; - m_worst_frame_time_accumulator = std::max(m_worst_frame_time_accumulator, frame_time); + const float frame_time = static_cast(s_frame_timer.GetTimeMilliseconds()); + s_average_frame_time_accumulator += frame_time; + s_worst_frame_time_accumulator = std::max(s_worst_frame_time_accumulator, frame_time); // update fps counter - const float time = static_cast(m_fps_timer.GetTimeSeconds()); + const float time = static_cast(s_fps_timer.GetTimeSeconds()); if (time < 1.0f) return; - const float frames_presented = static_cast(m_frame_number - m_last_frame_number); + const float frames_presented = static_cast(s_frame_number - s_last_frame_number); + const u32 global_tick_counter = TimingEvents::GetGlobalTickCounter(); - m_worst_frame_time = m_worst_frame_time_accumulator; - m_worst_frame_time_accumulator = 0.0f; - m_average_frame_time = m_average_frame_time_accumulator / frames_presented; - m_average_frame_time_accumulator = 0.0f; - m_vps = static_cast(frames_presented / time); - m_last_frame_number = m_frame_number; - m_fps = static_cast(m_internal_frame_number - m_last_internal_frame_number) / time; - m_last_internal_frame_number = m_internal_frame_number; - m_speed = static_cast(static_cast(m_global_tick_counter - m_last_global_tick_counter) / + s_worst_frame_time = s_worst_frame_time_accumulator; + s_worst_frame_time_accumulator = 0.0f; + s_average_frame_time = s_average_frame_time_accumulator / frames_presented; + s_average_frame_time_accumulator = 0.0f; + s_vps = static_cast(frames_presented / time); + s_last_frame_number = s_frame_number; + s_fps = static_cast(s_internal_frame_number - s_last_internal_frame_number) / time; + s_last_internal_frame_number = s_internal_frame_number; + s_speed = static_cast(static_cast(global_tick_counter - s_last_global_tick_counter) / (static_cast(MASTER_CLOCK) * time)) * 100.0f; - m_last_global_tick_counter = m_global_tick_counter; - m_fps_timer.Reset(); + s_last_global_tick_counter = global_tick_counter; + s_fps_timer.Reset(); - m_host_interface->OnSystemPerformanceCountersUpdated(); + g_host_interface->OnSystemPerformanceCountersUpdated(); } -void System::ResetPerformanceCounters() +void ResetPerformanceCounters() { - m_last_frame_number = m_frame_number; - m_last_internal_frame_number = m_internal_frame_number; - m_last_global_tick_counter = m_global_tick_counter; - m_average_frame_time_accumulator = 0.0f; - m_worst_frame_time_accumulator = 0.0f; - m_fps_timer.Reset(); - m_throttle_timer.Reset(); - m_last_throttle_time = 0; + s_last_frame_number = s_frame_number; + s_last_internal_frame_number = s_internal_frame_number; + s_last_global_tick_counter = TimingEvents::GetGlobalTickCounter(); + s_average_frame_time_accumulator = 0.0f; + s_worst_frame_time_accumulator = 0.0f; + s_fps_timer.Reset(); + s_throttle_timer.Reset(); + s_last_throttle_time = 0; } -bool System::LoadEXE(const char* filename, std::vector& bios_image) +bool LoadEXE(const char* filename, std::vector& bios_image) { std::FILE* fp = std::fopen(filename, "rb"); if (!fp) @@ -742,7 +899,7 @@ bool System::LoadEXE(const char* filename, std::vector& bios_image) u32 address = header.memfill_start & ~UINT32_C(3); for (u32 i = 0; i < words_to_write; i++) { - m_cpu->SafeWriteMemoryWord(address, 0); + CPU::SafeWriteMemoryWord(address, 0); address += sizeof(u32); } } @@ -760,7 +917,7 @@ bool System::LoadEXE(const char* filename, std::vector& bios_image) u32 address = header.load_address; for (u32 i = 0; i < num_words; i++) { - m_cpu->SafeWriteMemoryWord(address, data_words[i]); + CPU::SafeWriteMemoryWord(address, data_words[i]); address += sizeof(u32); } } @@ -775,7 +932,7 @@ bool System::LoadEXE(const char* filename, std::vector& bios_image) return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp); } -bool System::LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image) +bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image) { const u8* buffer_ptr = static_cast(buffer); const u8* buffer_end = static_cast(buffer) + buffer_size; @@ -796,7 +953,7 @@ bool System::LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector< u32 address = header.memfill_start & ~UINT32_C(3); for (u32 i = 0; i < words_to_write; i++) { - m_cpu->SafeWriteMemoryWord(address, 0); + CPU::SafeWriteMemoryWord(address, 0); address += sizeof(u32); } } @@ -813,7 +970,7 @@ bool System::LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector< u32 address = header.load_address; for (u32 i = 0; i < num_words; i++) { - m_cpu->SafeWriteMemoryWord(address, data_words[i]); + CPU::SafeWriteMemoryWord(address, data_words[i]); address += sizeof(u32); } } @@ -826,7 +983,7 @@ bool System::LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector< return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp); } -bool System::LoadPSF(const char* filename, std::vector& bios_image) +bool LoadPSF(const char* filename, std::vector& bios_image) { Log_InfoPrintf("Loading PSF file from '%s'", filename); @@ -838,7 +995,7 @@ bool System::LoadPSF(const char* filename, std::vector& bios_image) return LoadEXEFromBuffer(exe_data.data(), static_cast(exe_data.size()), bios_image); } -bool System::SetExpansionROM(const char* filename) +bool SetExpansionROM(const char* filename) { std::FILE* fp = std::fopen(filename, "rb"); if (!fp) @@ -862,73 +1019,71 @@ bool System::SetExpansionROM(const char* filename) std::fclose(fp); Log_InfoPrintf("Loaded expansion ROM from '%s': %u bytes", filename, size); - m_bus->SetExpansionROM(std::move(data)); + Bus::SetExpansionROM(std::move(data)); return true; } -void System::StallCPU(TickCount ticks) +void StallCPU(TickCount ticks) { - m_cpu->AddPendingTicks(ticks); + CPU::AddPendingTicks(ticks); #if 0 - if (m_cpu->GetPendingTicks() >= m_cpu->GetDowncount() && !m_running_events) + if (CPU::GetPendingTicks() >= CPU::GetDowncount() && !m_running_events) RunEvents(); #endif } -Controller* System::GetController(u32 slot) const +Controller* GetController(u32 slot) { - return m_pad->GetController(slot); + return g_pad.GetController(slot); } -void System::UpdateControllers() +void UpdateControllers() { - const Settings& settings = m_host_interface->GetSettings(); for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - m_pad->SetController(i, nullptr); + g_pad.SetController(i, nullptr); - const ControllerType type = settings.controller_types[i]; + const ControllerType type = g_settings.controller_types[i]; if (type != ControllerType::None) { - std::unique_ptr controller = Controller::Create(this, type, i); + std::unique_ptr controller = Controller::Create(type, i); if (controller) { - controller->LoadSettings(m_host_interface, TinyString::FromFormat("Controller%u", i + 1u)); - m_pad->SetController(i, std::move(controller)); + controller->LoadSettings(TinyString::FromFormat("Controller%u", i + 1u)); + g_pad.SetController(i, std::move(controller)); } } } } -void System::UpdateControllerSettings() +void UpdateControllerSettings() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - Controller* controller = m_pad->GetController(i); + Controller* controller = g_pad.GetController(i); if (controller) - controller->LoadSettings(m_host_interface, TinyString::FromFormat("Controller%u", i + 1u)); + controller->LoadSettings(TinyString::FromFormat("Controller%u", i + 1u)); } } -void System::ResetControllers() +void ResetControllers() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - Controller* controller = m_pad->GetController(i); + Controller* controller = g_pad.GetController(i); if (controller) controller->Reset(); } } -void System::UpdateMemoryCards() +void UpdateMemoryCards() { - const Settings& settings = m_host_interface->GetSettings(); for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - m_pad->SetMemoryCard(i, nullptr); + g_pad.SetMemoryCard(i, nullptr); std::unique_ptr card; - const MemoryCardType type = settings.memory_card_types[i]; + const MemoryCardType type = g_settings.memory_card_types[i]; switch (type) { case MemoryCardType::None: @@ -936,310 +1091,120 @@ void System::UpdateMemoryCards() case MemoryCardType::PerGame: { - if (m_running_game_code.empty()) + if (s_running_game_code.empty()) { - m_host_interface->AddFormattedOSDMessage(5.0f, + g_host_interface->AddFormattedOSDMessage(5.0f, "Per-game memory card cannot be used for slot %u as the running " "game has no code. Using shared card instead.", i + 1u); - card = MemoryCard::Open(this, m_host_interface->GetSharedMemoryCardPath(i)); + card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { - card = MemoryCard::Open(this, m_host_interface->GetGameMemoryCardPath(m_running_game_code.c_str(), i)); + card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(s_running_game_code.c_str(), i)); } } break; case MemoryCardType::PerGameTitle: { - if (m_running_game_title.empty()) + if (s_running_game_title.empty()) { - m_host_interface->AddFormattedOSDMessage(5.0f, + g_host_interface->AddFormattedOSDMessage(5.0f, "Per-game memory card cannot be used for slot %u as the running " "game has no title. Using shared card instead.", i + 1u); - card = MemoryCard::Open(this, m_host_interface->GetSharedMemoryCardPath(i)); + card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { - card = MemoryCard::Open(this, m_host_interface->GetGameMemoryCardPath(m_running_game_title.c_str(), i)); + card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(s_running_game_title.c_str(), i)); } } break; case MemoryCardType::Shared: { - if (settings.memory_card_paths[i].empty()) + if (g_settings.memory_card_paths[i].empty()) { - m_host_interface->AddFormattedOSDMessage(2.0f, "Memory card path for slot %u is missing, using default.", + g_host_interface->AddFormattedOSDMessage(2.0f, "Memory card path for slot %u is missing, using default.", i + 1u); - card = MemoryCard::Open(this, m_host_interface->GetSharedMemoryCardPath(i)); + card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { - card = MemoryCard::Open(this, settings.memory_card_paths[i]); + card = MemoryCard::Open(g_settings.memory_card_paths[i]); } } break; } if (card) - m_pad->SetMemoryCard(i, std::move(card)); + g_pad.SetMemoryCard(i, std::move(card)); } } -bool System::HasMedia() const +bool HasMedia() { - return m_cdrom->HasMedia(); + return g_cdrom.HasMedia(); } -bool System::InsertMedia(const char* path) +bool InsertMedia(const char* path) { std::unique_ptr image = OpenCDImage(path, false); if (!image) return false; UpdateRunningGame(path, image.get()); - m_cdrom->InsertMedia(std::move(image)); - Log_InfoPrintf("Inserted media from %s (%s, %s)", m_running_game_path.c_str(), m_running_game_code.c_str(), - m_running_game_title.c_str()); + g_cdrom.InsertMedia(std::move(image)); + Log_InfoPrintf("Inserted media from %s (%s, %s)", s_running_game_path.c_str(), s_running_game_code.c_str(), + s_running_game_title.c_str()); - if (GetSettings().HasAnyPerGameMemoryCards()) + if (g_settings.HasAnyPerGameMemoryCards()) { - m_host_interface->AddOSDMessage("Game changed, reloading memory cards.", 2.0f); + g_host_interface->AddOSDMessage("Game changed, reloading memory cards.", 2.0f); UpdateMemoryCards(); } return true; } -void System::RemoveMedia() +void RemoveMedia() { - m_cdrom->RemoveMedia(); + g_cdrom.RemoveMedia(); } -std::unique_ptr System::CreateTimingEvent(std::string name, TickCount period, TickCount interval, - TimingEventCallback callback, bool activate) +void UpdateRunningGame(const char* path, CDImage* image) { - std::unique_ptr event = - std::make_unique(this, std::move(name), period, interval, std::move(callback)); - if (activate) - event->Activate(); - - return event; -} - -static bool CompareEvents(const TimingEvent* lhs, const TimingEvent* rhs) -{ - return lhs->GetDowncount() > rhs->GetDowncount(); -} - -void System::AddActiveEvent(TimingEvent* event) -{ - m_events.push_back(event); - if (!m_running_events) - { - std::push_heap(m_events.begin(), m_events.end(), CompareEvents); - if (!m_frame_done) - UpdateCPUDowncount(); - } - else - { - m_events_need_sorting = true; - } -} - -void System::RemoveActiveEvent(TimingEvent* event) -{ - auto iter = std::find_if(m_events.begin(), m_events.end(), [event](const auto& it) { return event == it; }); - if (iter == m_events.end()) - { - Panic("Attempt to remove inactive event"); - return; - } - - m_events.erase(iter); - if (!m_running_events) - { - std::make_heap(m_events.begin(), m_events.end(), CompareEvents); - if (!m_events.empty() && !m_frame_done) - UpdateCPUDowncount(); - } - else - { - m_events_need_sorting = true; - } -} - -void System::SortEvents() -{ - if (!m_running_events) - { - std::make_heap(m_events.begin(), m_events.end(), CompareEvents); - if (!m_frame_done) - UpdateCPUDowncount(); - } - else - { - m_events_need_sorting = true; - } -} - -void System::RunEvents() -{ - DebugAssert(!m_running_events && !m_events.empty()); - - m_running_events = true; - - TickCount pending_ticks = (m_global_tick_counter + m_cpu->GetPendingTicks()) - m_last_event_run_time; - m_cpu->ResetPendingTicks(); - while (pending_ticks > 0) - { - const TickCount time = std::min(pending_ticks, m_events[0]->m_downcount); - m_global_tick_counter += static_cast(time); - pending_ticks -= time; - - // Apply downcount to all events. - // This will result in a negative downcount for those events which are late. - for (TimingEvent* evt : m_events) - { - evt->m_downcount -= time; - evt->m_time_since_last_run += time; - } - - // Now we can actually run the callbacks. - while (m_events.front()->GetDowncount() <= 0) - { - TimingEvent* evt = m_events.front(); - const TickCount ticks_late = -evt->m_downcount; - std::pop_heap(m_events.begin(), m_events.end(), CompareEvents); - - // Factor late time into the time for the next invocation. - const TickCount ticks_to_execute = evt->m_time_since_last_run; - evt->m_downcount += evt->m_interval; - evt->m_time_since_last_run = 0; - - // The cycles_late is only an indicator, it doesn't modify the cycles to execute. - evt->m_callback(ticks_to_execute, ticks_late); - - // Place it in the appropriate position in the queue. - if (m_events_need_sorting) - { - // Another event may have been changed by this event, or the interval/downcount changed. - std::make_heap(m_events.begin(), m_events.end(), CompareEvents); - m_events_need_sorting = false; - } - else - { - // Keep the event list in a heap. The event we just serviced will be in the last place, - // so we can use push_here instead of make_heap, which should be faster. - std::push_heap(m_events.begin(), m_events.end(), CompareEvents); - } - } - } - - m_last_event_run_time = m_global_tick_counter; - m_running_events = false; - m_cpu->SetDowncount(m_events.front()->GetDowncount()); -} - -void System::UpdateCPUDowncount() -{ - m_cpu->SetDowncount(m_events[0]->GetDowncount()); -} - -bool System::DoEventsState(StateWrapper& sw) -{ - if (sw.IsReading()) - { - // Load timestamps for the clock events. - // Any oneshot events should be recreated by the load state method, so we can fix up their times here. - u32 event_count = 0; - sw.Do(&event_count); - - for (u32 i = 0; i < event_count; i++) - { - std::string event_name; - TickCount downcount, time_since_last_run, period, interval; - sw.Do(&event_name); - sw.Do(&downcount); - sw.Do(&time_since_last_run); - sw.Do(&period); - sw.Do(&interval); - if (sw.HasError()) - return false; - - TimingEvent* event = FindActiveEvent(event_name.c_str()); - if (!event) - { - Log_WarningPrintf("Save state has event '%s', but couldn't find this event when loading.", event_name.c_str()); - continue; - } - - // Using reschedule is safe here since we call sort afterwards. - event->m_downcount = downcount; - event->m_time_since_last_run = time_since_last_run; - event->m_period = period; - event->m_interval = interval; - } - - sw.Do(&m_last_event_run_time); - - Log_DevPrintf("Loaded %u events from save state.", event_count); - SortEvents(); - } - else - { - u32 event_count = static_cast(m_events.size()); - sw.Do(&event_count); - - for (TimingEvent* evt : m_events) - { - sw.Do(&evt->m_name); - sw.Do(&evt->m_downcount); - sw.Do(&evt->m_time_since_last_run); - sw.Do(&evt->m_period); - sw.Do(&evt->m_interval); - } - - sw.Do(&m_last_event_run_time); - - Log_DevPrintf("Wrote %u events to save state.", event_count); - } - - return !sw.HasError(); -} - -TimingEvent* System::FindActiveEvent(const char* name) -{ - auto iter = - std::find_if(m_events.begin(), m_events.end(), [&name](auto& ev) { return ev->GetName().compare(name) == 0; }); - - return (iter != m_events.end()) ? *iter : nullptr; -} - -void System::UpdateRunningGame(const char* path, CDImage* image) -{ - m_running_game_path.clear(); - m_running_game_code.clear(); - m_running_game_title.clear(); + s_running_game_path.clear(); + s_running_game_code.clear(); + s_running_game_title.clear(); if (path && std::strlen(path) > 0) { - m_running_game_path = path; - m_host_interface->GetGameInfo(path, image, &m_running_game_code, &m_running_game_title); + s_running_game_path = path; + g_host_interface->GetGameInfo(path, image, &s_running_game_code, &s_running_game_title); } - m_host_interface->OnRunningGameChanged(); + g_host_interface->OnRunningGameChanged(); } -u32 System::GetMediaPlaylistIndex() const +u32 GetMediaPlaylistCount() { - if (!m_cdrom->HasMedia()) + return static_cast(m_media_playlist.size()); +} + +const std::string& GetMediaPlaylistPath(u32 index) +{ + return m_media_playlist[index]; +} + +u32 GetMediaPlaylistIndex() +{ + if (!g_cdrom.HasMedia()) return std::numeric_limits::max(); - const std::string& media_path = m_cdrom->GetMediaFileName(); + const std::string& media_path = g_cdrom.GetMediaFileName(); for (u32 i = 0; i < static_cast(m_media_playlist.size()); i++) { if (m_media_playlist[i] == media_path) @@ -1249,7 +1214,7 @@ u32 System::GetMediaPlaylistIndex() const return std::numeric_limits::max(); } -bool System::AddMediaPathToPlaylist(const std::string_view& path) +bool AddMediaPathToPlaylist(const std::string_view& path) { if (std::any_of(m_media_playlist.begin(), m_media_playlist.end(), [&path](const std::string& p) { return (path == p); })) @@ -1261,7 +1226,7 @@ bool System::AddMediaPathToPlaylist(const std::string_view& path) return true; } -bool System::RemoveMediaPathFromPlaylist(const std::string_view& path) +bool RemoveMediaPathFromPlaylist(const std::string_view& path) { for (u32 i = 0; i < static_cast(m_media_playlist.size()); i++) { @@ -1272,30 +1237,30 @@ bool System::RemoveMediaPathFromPlaylist(const std::string_view& path) return false; } -bool System::RemoveMediaPathFromPlaylist(u32 index) +bool RemoveMediaPathFromPlaylist(u32 index) { if (index >= static_cast(m_media_playlist.size())) return false; if (GetMediaPlaylistIndex() == index) { - m_host_interface->ReportMessage("Removing current media from playlist, removing media from CD-ROM."); - m_cdrom->RemoveMedia(); + g_host_interface->ReportMessage("Removing current media from playlist, removing media from CD-ROM."); + g_cdrom.RemoveMedia(); } m_media_playlist.erase(m_media_playlist.begin() + index); return true; } -bool System::ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path) +bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path) { if (index >= static_cast(m_media_playlist.size())) return false; if (GetMediaPlaylistIndex() == index) { - m_host_interface->ReportMessage("Changing current media from playlist, replacing current media."); - m_cdrom->RemoveMedia(); + g_host_interface->ReportMessage("Changing current media from playlist, replacing current media."); + g_cdrom.RemoveMedia(); m_media_playlist[index] = path; InsertMedia(m_media_playlist[index].c_str()); @@ -1308,14 +1273,16 @@ bool System::ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& pat return true; } -bool System::SwitchMediaFromPlaylist(u32 index) +bool SwitchMediaFromPlaylist(u32 index) { if (index >= m_media_playlist.size()) return false; const std::string& path = m_media_playlist[index]; - if (m_cdrom->HasMedia() && m_cdrom->GetMediaFileName() == path) + if (g_cdrom.HasMedia() && g_cdrom.GetMediaFileName() == path) return true; return InsertMedia(path.c_str()); } + +} // namespace System \ No newline at end of file diff --git a/src/core/system.h b/src/core/system.h index 29378afe6..1f0585016 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -11,22 +11,7 @@ class ByteStream; class CDImage; class StateWrapper; -namespace CPU { -class Core; -class CodeCache; -} // namespace CPU - -class Bus; -class DMA; -class InterruptController; -class GPU; -class CDROM; -class Pad; class Controller; -class Timers; -class SPU; -class MDEC; -class SIO; struct SystemBootParameters { @@ -44,227 +29,105 @@ struct SystemBootParameters bool force_software_renderer = false; }; -class System +namespace System { + +enum : u32 { -public: - enum : u32 - { - // 5 megabytes is sufficient for now, at the moment they're around 4.2MB. - MAX_SAVE_STATE_SIZE = 5 * 1024 * 1024 - }; - - friend TimingEvent; - - ~System(); - - /// Returns the preferred console type for a disc. - static ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); - - /// Creates a new System. - static std::unique_ptr Create(HostInterface* host_interface); - - // Accessing components. - HostInterface* GetHostInterface() const { return m_host_interface; } - CPU::Core* GetCPU() const { return m_cpu.get(); } - Bus* GetBus() const { return m_bus.get(); } - DMA* GetDMA() const { return m_dma.get(); } - InterruptController* GetInterruptController() const { return m_interrupt_controller.get(); } - GPU* GetGPU() const { return m_gpu.get(); } - CDROM* GetCDROM() const { return m_cdrom.get(); } - Pad* GetPad() const { return m_pad.get(); } - Timers* GetTimers() const { return m_timers.get(); } - SPU* GetSPU() const { return m_spu.get(); } - MDEC* GetMDEC() const { return m_mdec.get(); } - - ConsoleRegion GetRegion() const { return m_region; } - bool IsPALRegion() const { return m_region == ConsoleRegion::PAL; } - u32 GetFrameNumber() const { return m_frame_number; } - u32 GetInternalFrameNumber() const { return m_internal_frame_number; } - u32 GetGlobalTickCounter() const { return m_global_tick_counter; } - void IncrementFrameNumber() - { - m_frame_number++; - m_frame_done = true; - } - void IncrementInternalFrameNumber() { m_internal_frame_number++; } - - const Settings& GetSettings() { return m_host_interface->GetSettings(); } - - const std::string& GetRunningPath() const { return m_running_game_path; } - const std::string& GetRunningCode() const { return m_running_game_code; } - const std::string& GetRunningTitle() const { return m_running_game_title; } - - float GetFPS() const { return m_fps; } - float GetVPS() const { return m_vps; } - float GetEmulationSpeed() const { return m_speed; } - float GetAverageFrameTime() const { return m_average_frame_time; } - float GetWorstFrameTime() const { return m_worst_frame_time; } - float GetThrottleFrequency() const { return m_throttle_frequency; } - - bool Boot(const SystemBootParameters& params); - void Reset(); - - bool LoadState(ByteStream* state); - bool SaveState(ByteStream* state, u32 screenshot_size = 128); - - /// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes. - bool RecreateGPU(GPURenderer renderer); - - /// Updates GPU settings, without recreating the renderer. - void UpdateGPUSettings(); - - /// Forcibly changes the CPU execution mode, ignoring settings. - void SetCPUExecutionMode(CPUExecutionMode mode); - - void RunFrame(); - - /// Adjusts the throttle frequency, i.e. how many times we should sleep per second. - void SetThrottleFrequency(float frequency); - - /// Updates the throttle period, call when target emulation speed changes. - void UpdateThrottlePeriod(); - - /// Throttles the system, i.e. sleeps until it's time to execute the next frame. - void Throttle(); - - void UpdatePerformanceCounters(); - void ResetPerformanceCounters(); - - bool LoadEXE(const char* filename, std::vector& bios_image); - bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image); - bool LoadPSF(const char* filename, std::vector& bios_image); - bool SetExpansionROM(const char* filename); - - // Adds ticks to the global tick counter, simulating the CPU being stalled. - void StallCPU(TickCount ticks); - - // Access controllers for simulating input. - Controller* GetController(u32 slot) const; - void UpdateControllers(); - void UpdateControllerSettings(); - void ResetControllers(); - void UpdateMemoryCards(); - - bool HasMedia() const; - bool InsertMedia(const char* path); - void RemoveMedia(); - - /// Creates a new event. - std::unique_ptr CreateTimingEvent(std::string name, TickCount period, TickCount interval, - TimingEventCallback callback, bool activate); - - /// Returns the number of entries in the media/disc playlist. - ALWAYS_INLINE u32 GetMediaPlaylistCount() const { return static_cast(m_media_playlist.size()); } - - /// Returns the current image from the media/disc playlist. - u32 GetMediaPlaylistIndex() const; - - /// Returns the path to the specified playlist index. - const std::string& GetMediaPlaylistPath(u32 index) const { return m_media_playlist[index]; } - - /// Adds a new path to the media playlist. - bool AddMediaPathToPlaylist(const std::string_view& path); - - /// Removes a path from the media playlist. - bool RemoveMediaPathFromPlaylist(const std::string_view& path); - bool RemoveMediaPathFromPlaylist(u32 index); - - /// Changes a path from the media playlist. - bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path); - - /// Switches to the specified media/disc playlist index. - bool SwitchMediaFromPlaylist(u32 index); - -private: - System(HostInterface* host_interface); - - /// Opens CD image, preloading if needed. - std::unique_ptr OpenCDImage(const char* path, bool force_preload); - - bool DoLoadState(ByteStream* stream, bool init_components, bool force_software_renderer); - bool DoState(StateWrapper& sw); - bool CreateGPU(GPURenderer renderer); - - bool InitializeComponents(bool force_software_renderer); - void DestroyComponents(); - - // Active event management - void AddActiveEvent(TimingEvent* event); - void RemoveActiveEvent(TimingEvent* event); - void SortEvents(); - - // Runs any pending events. Call when CPU downcount is zero. - void RunEvents(); - - // Updates the downcount of the CPU (event scheduling). - void UpdateCPUDowncount(); - - bool DoEventsState(StateWrapper& sw); - - // Event lookup, use with care. - // If you modify an event, call SortEvents afterwards. - TimingEvent* FindActiveEvent(const char* name); - - // Event enumeration, use with care. - // Don't remove an event while enumerating the list, as it will invalidate the iterator. - template - void EnumerateActiveEvents(T callback) const - { - for (const TimingEvent* ev : m_events) - callback(ev); - } - - void UpdateRunningGame(const char* path, CDImage* image); - - HostInterface* m_host_interface; - std::unique_ptr m_cpu; - std::unique_ptr m_cpu_code_cache; - std::unique_ptr m_bus; - std::unique_ptr m_dma; - std::unique_ptr m_interrupt_controller; - std::unique_ptr m_gpu; - std::unique_ptr m_cdrom; - std::unique_ptr m_pad; - std::unique_ptr m_timers; - std::unique_ptr m_spu; - std::unique_ptr m_mdec; - std::unique_ptr m_sio; - ConsoleRegion m_region = ConsoleRegion::NTSC_U; - CPUExecutionMode m_cpu_execution_mode = CPUExecutionMode::Interpreter; - u32 m_frame_number = 1; - u32 m_internal_frame_number = 1; - u32 m_global_tick_counter = 0; - - std::vector m_events; - u32 m_last_event_run_time = 0; - bool m_running_events = false; - bool m_events_need_sorting = false; - bool m_frame_done = false; - - std::string m_running_game_path; - std::string m_running_game_code; - std::string m_running_game_title; - - float m_throttle_frequency = 60.0f; - s32 m_throttle_period = 0; - u64 m_last_throttle_time = 0; - Common::Timer m_throttle_timer; - Common::Timer m_speed_lost_time_timestamp; - - float m_average_frame_time_accumulator = 0.0f; - float m_worst_frame_time_accumulator = 0.0f; - - float m_vps = 0.0f; - float m_fps = 0.0f; - float m_speed = 0.0f; - float m_worst_frame_time = 0.0f; - float m_average_frame_time = 0.0f; - u32 m_last_frame_number = 0; - u32 m_last_internal_frame_number = 0; - u32 m_last_global_tick_counter = 0; - Common::Timer m_fps_timer; - Common::Timer m_frame_timer; - - // Playlist of disc images. - std::vector m_media_playlist; + // 5 megabytes is sufficient for now, at the moment they're around 4.2MB. + MAX_SAVE_STATE_SIZE = 5 * 1024 * 1024 }; + +enum class State +{ + Shutdown, + Starting, + Running, + Paused +}; + +/// Returns the preferred console type for a disc. +ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); + +State GetState(); +void SetState(State new_state); +bool IsRunning(); +bool IsPaused(); +bool IsShutdown(); +bool IsValid(); + +ConsoleRegion GetRegion(); +bool IsPALRegion(); +u32 GetFrameNumber(); +u32 GetInternalFrameNumber(); +void FrameDone(); +void IncrementInternalFrameNumber(); + +const std::string& GetRunningPath(); +const std::string& GetRunningCode(); +const std::string& GetRunningTitle(); + +float GetFPS(); +float GetVPS(); +float GetEmulationSpeed(); +float GetAverageFrameTime(); +float GetWorstFrameTime(); +float GetThrottleFrequency(); + +bool Boot(const SystemBootParameters& params); +void Reset(); +void Shutdown(); + +bool LoadState(ByteStream* state); +bool SaveState(ByteStream* state, u32 screenshot_size = 128); + +/// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes. +bool RecreateGPU(GPURenderer renderer); + +void RunFrame(); + +/// Adjusts the throttle frequency, i.e. how many times we should sleep per second. +void SetThrottleFrequency(float frequency); + +/// Updates the throttle period, call when target emulation speed changes. +void UpdateThrottlePeriod(); + +/// Throttles the system, i.e. sleeps until it's time to execute the next frame. +void Throttle(); + +void UpdatePerformanceCounters(); +void ResetPerformanceCounters(); + +// Access controllers for simulating input. +Controller* GetController(u32 slot); +void UpdateControllers(); +void UpdateControllerSettings(); +void ResetControllers(); +void UpdateMemoryCards(); + +bool HasMedia(); +bool InsertMedia(const char* path); +void RemoveMedia(); + +/// Returns the number of entries in the media/disc playlist. +u32 GetMediaPlaylistCount(); + +/// Returns the current image from the media/disc playlist. +u32 GetMediaPlaylistIndex(); + +/// Returns the path to the specified playlist index. +const std::string& GetMediaPlaylistPath(u32 index); + +/// Adds a new path to the media playlist. +bool AddMediaPathToPlaylist(const std::string_view& path); + +/// Removes a path from the media playlist. +bool RemoveMediaPathFromPlaylist(const std::string_view& path); +bool RemoveMediaPathFromPlaylist(u32 index); + +/// Changes a path from the media playlist. +bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path); + +/// Switches to the specified media/disc playlist index. +bool SwitchMediaFromPlaylist(u32 index); + +} // namespace System diff --git a/src/core/timers.cpp b/src/core/timers.cpp index 6edfa6af4..0fad8957c 100644 --- a/src/core/timers.cpp +++ b/src/core/timers.cpp @@ -7,17 +7,22 @@ #include Log_SetChannel(Timers); +Timers g_timers; + Timers::Timers() = default; Timers::~Timers() = default; -void Timers::Initialize(System* system, InterruptController* interrupt_controller, GPU* gpu) +void Timers::Initialize() { - m_system = system; - m_interrupt_controller = interrupt_controller; - m_gpu = gpu; - m_sysclk_event = system->CreateTimingEvent("Timer SysClk Interrupt", 1, 1, - std::bind(&Timers::AddSysClkTicks, this, std::placeholders::_1), false); + m_sysclk_event = TimingEvents::CreateTimingEvent( + "Timer SysClk Interrupt", 1, 1, std::bind(&Timers::AddSysClkTicks, this, std::placeholders::_1), false); + Reset(); +} + +void Timers::Shutdown() +{ + m_sysclk_event.reset(); } void Timers::Reset() @@ -182,8 +187,8 @@ u32 Timers::ReadRegister(u32 offset) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || m_gpu->IsCRTCScanlinePending()) - m_gpu->SynchronizeCRTC(); + if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) + g_gpu->SynchronizeCRTC(); } m_sysclk_event->InvokeEarly(); @@ -196,8 +201,8 @@ u32 Timers::ReadRegister(u32 offset) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || m_gpu->IsCRTCScanlinePending()) - m_gpu->SynchronizeCRTC(); + if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) + g_gpu->SynchronizeCRTC(); } m_sysclk_event->InvokeEarly(); @@ -227,8 +232,8 @@ void Timers::WriteRegister(u32 offset, u32 value) if (timer_index < 2 && cs.external_counting_enabled) { // timers 0/1 depend on the GPU - if (timer_index == 0 || m_gpu->IsCRTCScanlinePending()) - m_gpu->SynchronizeCRTC(); + if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) + g_gpu->SynchronizeCRTC(); } m_sysclk_event->InvokeEarly(); @@ -311,7 +316,7 @@ void Timers::UpdateIRQ(u32 index) Log_DebugPrintf("Raising timer %u IRQ", index); cs.irq_done = true; - m_interrupt_controller->InterruptRequest( + g_interrupt_controller.InterruptRequest( static_cast(static_cast(InterruptController::IRQ::TMR0) + index)); } @@ -367,7 +372,7 @@ void Timers::DrawDebugStateWindow() const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 100.0f * framebuffer_scale), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Timer State", &m_system->GetSettings().debugging.show_timers_state)) + if (!ImGui::Begin("Timer State", &g_settings.debugging.show_timers_state)) { ImGui::End(); return; diff --git a/src/core/timers.h b/src/core/timers.h index 488877d8e..f0c1348cf 100644 --- a/src/core/timers.h +++ b/src/core/timers.h @@ -6,18 +6,17 @@ class StateWrapper; -class System; class TimingEvent; -class InterruptController; class GPU; -class Timers +class Timers final { public: Timers(); ~Timers(); - void Initialize(System* system, InterruptController* interrupt_controller, GPU* gpu); + void Initialize(); + void Shutdown(); void Reset(); bool DoState(StateWrapper& sw); @@ -26,10 +25,10 @@ public: void DrawDebugStateWindow(); // dot clock/hblank/sysclk div 8 - bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; } + ALWAYS_INLINE bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; } // queries for GPU - bool IsExternalIRQEnabled(u32 timer) const + ALWAYS_INLINE bool IsExternalIRQEnabled(u32 timer) const { const CounterState& cs = m_states[timer]; return (cs.external_counting_enabled && (cs.mode.bits & ((1u << 4) | (1u << 5))) != 0); @@ -42,9 +41,6 @@ public: u32 ReadRegister(u32 offset); void WriteRegister(u32 offset, u32 value); - // changing interfaces - void SetGPU(GPU* gpu) { m_gpu = gpu; } - private: static constexpr u32 NUM_TIMERS = 3; @@ -93,11 +89,10 @@ private: TickCount GetTicksUntilNextInterrupt() const; void UpdateSysClkEvent(); - System* m_system = nullptr; - InterruptController* m_interrupt_controller = nullptr; - GPU* m_gpu = nullptr; std::unique_ptr m_sysclk_event; std::array m_states{}; u32 m_sysclk_div_8_carry = 0; // partial ticks for timer 3 with sysclk/8 }; + +extern Timers g_timers; \ No newline at end of file diff --git a/src/core/timing_event.cpp b/src/core/timing_event.cpp index 94903d260..583f7e32c 100644 --- a/src/core/timing_event.cpp +++ b/src/core/timing_event.cpp @@ -1,34 +1,268 @@ #include "timing_event.h" #include "common/assert.h" +#include "common/log.h" +#include "common/state_wrapper.h" #include "cpu_core.h" #include "system.h" +Log_SetChannel(TimingEvents); -TimingEvent::TimingEvent(System* system, std::string name, TickCount period, TickCount interval, - TimingEventCallback callback) +namespace TimingEvents { + +static std::vector s_events; +static u32 s_global_tick_counter = 0; +static u32 s_last_event_run_time = 0; +static bool s_running_events = false; +static bool s_events_need_sorting = false; + +u32 GetGlobalTickCounter() +{ + return s_global_tick_counter; +} + +void Initialize() +{ + Reset(); +} + +void Reset() +{ + s_global_tick_counter = 0; + s_last_event_run_time = 0; +} + +void Shutdown() +{ + Assert(s_events.empty()); +} + +std::unique_ptr CreateTimingEvent(std::string name, TickCount period, TickCount interval, + TimingEventCallback callback, bool activate) +{ + std::unique_ptr event = + std::make_unique(std::move(name), period, interval, std::move(callback)); + if (activate) + event->Activate(); + + return event; +} + +void UpdateCPUDowncount() +{ + if (!CPU::g_state.frame_done) + CPU::g_state.downcount = s_events[0]->GetDowncount(); +} + +static bool CompareEvents(const TimingEvent* lhs, const TimingEvent* rhs) +{ + return lhs->GetDowncount() > rhs->GetDowncount(); +} + +static void AddActiveEvent(TimingEvent* event) +{ + s_events.push_back(event); + if (!s_running_events) + { + std::push_heap(s_events.begin(), s_events.end(), CompareEvents); + UpdateCPUDowncount(); + } + else + { + s_events_need_sorting = true; + } +} + +static void RemoveActiveEvent(TimingEvent* event) +{ + auto iter = std::find_if(s_events.begin(), s_events.end(), [event](const auto& it) { return event == it; }); + if (iter == s_events.end()) + { + Panic("Attempt to remove inactive event"); + return; + } + + s_events.erase(iter); + if (!s_running_events) + { + std::make_heap(s_events.begin(), s_events.end(), CompareEvents); + if (!s_events.empty()) + UpdateCPUDowncount(); + } + else + { + s_events_need_sorting = true; + } +} + +static TimingEvent* FindActiveEvent(const char* name) +{ + auto iter = + std::find_if(s_events.begin(), s_events.end(), [&name](auto& ev) { return ev->GetName().compare(name) == 0; }); + + return (iter != s_events.end()) ? *iter : nullptr; +} + +static void SortEvents() +{ + if (!s_running_events) + { + std::make_heap(s_events.begin(), s_events.end(), CompareEvents); + UpdateCPUDowncount(); + } + else + { + s_events_need_sorting = true; + } +} + +void RunEvents() +{ + DebugAssert(!s_running_events && !s_events.empty()); + + s_running_events = true; + + TickCount pending_ticks = (s_global_tick_counter + CPU::GetPendingTicks()) - s_last_event_run_time; + CPU::ResetPendingTicks(); + while (pending_ticks > 0) + { + const TickCount time = std::min(pending_ticks, s_events[0]->GetDowncount()); + s_global_tick_counter += static_cast(time); + pending_ticks -= time; + + // Apply downcount to all events. + // This will result in a negative downcount for those events which are late. + for (TimingEvent* evt : s_events) + { + evt->m_downcount -= time; + evt->m_time_since_last_run += time; + } + + // Now we can actually run the callbacks. + while (s_events.front()->m_downcount <= 0) + { + TimingEvent* evt = s_events.front(); + std::pop_heap(s_events.begin(), s_events.end(), CompareEvents); + + // Factor late time into the time for the next invocation. + const TickCount ticks_late = -evt->m_downcount; + const TickCount ticks_to_execute = evt->m_time_since_last_run; + evt->m_downcount += evt->m_interval; + evt->m_time_since_last_run = 0; + + // The cycles_late is only an indicator, it doesn't modify the cycles to execute. + evt->m_callback(ticks_to_execute, ticks_late); + + // Place it in the appropriate position in the queue. + if (s_events_need_sorting) + { + // Another event may have been changed by this event, or the interval/downcount changed. + std::make_heap(s_events.begin(), s_events.end(), CompareEvents); + s_events_need_sorting = false; + } + else + { + // Keep the event list in a heap. The event we just serviced will be in the last place, + // so we can use push_here instead of make_heap, which should be faster. + std::push_heap(s_events.begin(), s_events.end(), CompareEvents); + } + } + } + + s_last_event_run_time = s_global_tick_counter; + s_running_events = false; + UpdateCPUDowncount(); +} + +bool DoState(StateWrapper& sw, u32 global_tick_counter) +{ + if (sw.IsReading()) + { + s_global_tick_counter = global_tick_counter; + + // Load timestamps for the clock events. + // Any oneshot events should be recreated by the load state method, so we can fix up their times here. + u32 event_count = 0; + sw.Do(&event_count); + + for (u32 i = 0; i < event_count; i++) + { + std::string event_name; + TickCount downcount, time_since_last_run, period, interval; + sw.Do(&event_name); + sw.Do(&downcount); + sw.Do(&time_since_last_run); + sw.Do(&period); + sw.Do(&interval); + if (sw.HasError()) + return false; + + TimingEvent* event = FindActiveEvent(event_name.c_str()); + if (!event) + { + Log_WarningPrintf("Save state has event '%s', but couldn't find this event when loading.", event_name.c_str()); + continue; + } + + // Using reschedule is safe here since we call sort afterwards. + event->m_downcount = downcount; + event->m_time_since_last_run = time_since_last_run; + event->m_period = period; + event->m_interval = interval; + } + + sw.Do(&s_last_event_run_time); + + Log_DevPrintf("Loaded %u events from save state.", event_count); + SortEvents(); + } + else + { + u32 event_count = static_cast(s_events.size()); + sw.Do(&event_count); + + for (TimingEvent* evt : s_events) + { + sw.Do(&evt->m_name); + sw.Do(&evt->m_downcount); + sw.Do(&evt->m_time_since_last_run); + sw.Do(&evt->m_period); + sw.Do(&evt->m_interval); + } + + sw.Do(&s_last_event_run_time); + + Log_DevPrintf("Wrote %u events to save state.", event_count); + } + + return !sw.HasError(); +} + +} // namespace TimingEvents + +TimingEvent::TimingEvent(std::string name, TickCount period, TickCount interval, TimingEventCallback callback) : m_downcount(interval), m_time_since_last_run(0), m_period(period), m_interval(interval), - m_callback(std::move(callback)), m_system(system), m_name(std::move(name)), m_active(false) + m_callback(std::move(callback)), m_name(std::move(name)), m_active(false) { } TimingEvent::~TimingEvent() { if (m_active) - m_system->RemoveActiveEvent(this); + TimingEvents::RemoveActiveEvent(this); } TickCount TimingEvent::GetTicksSinceLastExecution() const { - return m_system->m_cpu->GetPendingTicks() + m_time_since_last_run; + return CPU::GetPendingTicks() + m_time_since_last_run; } TickCount TimingEvent::GetTicksUntilNextExecution() const { - return std::max(m_downcount - m_system->m_cpu->GetPendingTicks(), static_cast(0)); + return std::max(m_downcount - CPU::GetPendingTicks(), static_cast(0)); } void TimingEvent::Schedule(TickCount ticks) { - const TickCount pending_ticks = m_system->m_cpu->GetPendingTicks(); + const TickCount pending_ticks = CPU::GetPendingTicks(); m_downcount = pending_ticks + ticks; if (!m_active) @@ -36,13 +270,13 @@ void TimingEvent::Schedule(TickCount ticks) // Event is going active, so we want it to only execute ticks from the current timestamp. m_time_since_last_run = -pending_ticks; m_active = true; - m_system->AddActiveEvent(this); + TimingEvents::AddActiveEvent(this); } else { // Event is already active, so we leave the time since last run alone, and just modify the downcount. // If this is a call from an IO handler for example, re-sort the event queue. - m_system->SortEvents(); + TimingEvents::SortEvents(); } } @@ -66,7 +300,7 @@ void TimingEvent::Reset() m_downcount = m_interval; m_time_since_last_run = 0; - m_system->SortEvents(); + TimingEvents::SortEvents(); } void TimingEvent::InvokeEarly(bool force /* = false */) @@ -74,7 +308,7 @@ void TimingEvent::InvokeEarly(bool force /* = false */) if (!m_active) return; - const TickCount pending_ticks = m_system->m_cpu->GetPendingTicks(); + const TickCount pending_ticks = CPU::GetPendingTicks(); const TickCount ticks_to_execute = m_time_since_last_run + pending_ticks; if (!force && ticks_to_execute < m_period) return; @@ -84,7 +318,7 @@ void TimingEvent::InvokeEarly(bool force /* = false */) m_callback(ticks_to_execute, 0); // Since we've changed the downcount, we need to re-sort the events. - m_system->SortEvents(); + TimingEvents::SortEvents(); } void TimingEvent::Activate() @@ -93,12 +327,12 @@ void TimingEvent::Activate() return; // leave the downcount intact - const TickCount pending_ticks = m_system->m_cpu->GetPendingTicks(); + const TickCount pending_ticks = CPU::GetPendingTicks(); m_downcount += pending_ticks; m_time_since_last_run -= pending_ticks; m_active = true; - m_system->AddActiveEvent(this); + TimingEvents::AddActiveEvent(this); } void TimingEvent::Deactivate() @@ -106,10 +340,10 @@ void TimingEvent::Deactivate() if (!m_active) return; - const TickCount pending_ticks = m_system->m_cpu->GetPendingTicks(); + const TickCount pending_ticks = CPU::GetPendingTicks(); m_downcount -= pending_ticks; m_time_since_last_run += pending_ticks; m_active = false; - m_system->RemoveActiveEvent(this); + TimingEvents::RemoveActiveEvent(this); } diff --git a/src/core/timing_event.h b/src/core/timing_event.h index b7070c77b..200fbee41 100644 --- a/src/core/timing_event.h +++ b/src/core/timing_event.h @@ -6,29 +6,24 @@ #include "types.h" -class System; -class TimingEvent; +class StateWrapper; // Event callback type. Second parameter is the number of cycles the event was executed "late". using TimingEventCallback = std::function; class TimingEvent { - friend System; - public: - TimingEvent(System* system, std::string name, TickCount period, TickCount interval, TimingEventCallback callback); + TimingEvent(std::string name, TickCount period, TickCount interval, TimingEventCallback callback); ~TimingEvent(); - System* GetSystem() const { return m_system; } const std::string& GetName() const { return m_name; } bool IsActive() const { return m_active; } // Returns the number of ticks between each event. - TickCount GetPeriod() const { return m_period; } - TickCount GetInterval() const { return m_interval; } - - TickCount GetDowncount() const { return m_downcount; } + ALWAYS_INLINE TickCount GetPeriod() const { return m_period; } + ALWAYS_INLINE TickCount GetInterval() const { return m_interval; } + ALWAYS_INLINE TickCount GetDowncount() const { return m_downcount; } // Includes pending time. TickCount GetTicksSinceLastExecution() const; @@ -61,14 +56,35 @@ public: void SetInterval(TickCount interval) { m_interval = interval; } void SetPeriod(TickCount period) { m_period = period; } -private: TickCount m_downcount; TickCount m_time_since_last_run; TickCount m_period; TickCount m_interval; TimingEventCallback m_callback; - System* m_system; std::string m_name; bool m_active; }; + +namespace TimingEvents { + +u32 GetGlobalTickCounter(); + +void Initialize(); +void Reset(); +void Shutdown(); + +/// Creates a new event. +std::unique_ptr CreateTimingEvent(std::string name, TickCount period, TickCount interval, + TimingEventCallback callback, bool activate); + +/// Serialization. +bool DoState(StateWrapper& sw, u32 global_tick_counter); + +void RunEvents(); + +void UpdateCPUDowncount(); + + + +} // namespace TimingEventManager \ No newline at end of file diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index e2f1fcdc7..4f7f1c6a9 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -162,13 +162,13 @@ void LibretroHostInterface::AddOSDMessage(std::string message, float duration /* { retro_message msg = {}; msg.msg = message.c_str(); - msg.frames = static_cast(duration * (m_system ? m_system->GetThrottleFrequency() : 60.0f)); + msg.frames = static_cast(duration * (System::IsShutdown() ? 60.0f : System::GetThrottleFrequency())); g_retro_environment_callback(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } void LibretroHostInterface::retro_get_system_av_info(struct retro_system_av_info* info) { - const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); + const bool use_resolution_scale = (g_settings.gpu_renderer != GPURenderer::Software); GetSystemAVInfo(info, use_resolution_scale); Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f, fps = %.2f", info->geometry.base_width, @@ -178,18 +178,18 @@ void LibretroHostInterface::retro_get_system_av_info(struct retro_system_av_info void LibretroHostInterface::GetSystemAVInfo(struct retro_system_av_info* info, bool use_resolution_scale) { - const u32 resolution_scale = use_resolution_scale ? m_settings.gpu_resolution_scale : 1u; - Assert(m_system); + const u32 resolution_scale = use_resolution_scale ? g_settings.gpu_resolution_scale : 1u; + Assert(System::IsValid()); std::memset(info, 0, sizeof(*info)); - info->geometry.aspect_ratio = Settings::GetDisplayAspectRatioValue(m_settings.display_aspect_ratio); + info->geometry.aspect_ratio = Settings::GetDisplayAspectRatioValue(g_settings.display_aspect_ratio); info->geometry.base_width = 320; info->geometry.base_height = 240; info->geometry.max_width = GPU::VRAM_WIDTH * resolution_scale; info->geometry.max_height = GPU::VRAM_HEIGHT * resolution_scale; - info->timing.fps = m_system->GetThrottleFrequency(); + info->timing.fps = System::GetThrottleFrequency(); info->timing.sample_rate = static_cast(AUDIO_SAMPLE_RATE); } @@ -209,7 +209,7 @@ void LibretroHostInterface::UpdateSystemAVInfo(bool use_resolution_scale) void LibretroHostInterface::UpdateGeometry() { struct retro_system_av_info avi; - const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); + const bool use_resolution_scale = (g_settings.gpu_renderer != GPURenderer::Software); GetSystemAVInfo(&avi, use_resolution_scale); Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f", avi.geometry.base_width, avi.geometry.base_height, @@ -221,12 +221,12 @@ void LibretroHostInterface::UpdateGeometry() void LibretroHostInterface::UpdateLogging() { - Log::SetFilterLevel(m_settings.log_level); + Log::SetFilterLevel(g_settings.log_level); if (s_libretro_log_callback_valid) Log::SetConsoleOutputParams(false); else - Log::SetConsoleOutputParams(true, nullptr, m_settings.log_level); + Log::SetConsoleOutputParams(true, nullptr, g_settings.log_level); } bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game) @@ -239,7 +239,7 @@ bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game) if (!BootSystem(bp)) return false; - if (m_settings.gpu_renderer != GPURenderer::Software) + if (g_settings.gpu_renderer != GPURenderer::Software) { if (!m_hw_render_callback_valid) RequestHardwareRendererContext(); @@ -252,25 +252,25 @@ bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game) void LibretroHostInterface::retro_run_frame() { - Assert(m_system); + Assert(!System::IsShutdown()); if (HasCoreVariablesChanged()) UpdateSettings(); UpdateControllers(); - m_system->GetGPU()->RestoreGraphicsAPIState(); + g_gpu->RestoreGraphicsAPIState(); - m_system->RunFrame(); + System::RunFrame(); - m_system->GetGPU()->ResetGraphicsAPIState(); + g_gpu->ResetGraphicsAPIState(); m_display->Render(); } unsigned LibretroHostInterface::retro_get_region() { - return m_system->IsPALRegion() ? RETRO_REGION_PAL : RETRO_REGION_NTSC; + return System::IsPALRegion() ? RETRO_REGION_PAL : RETRO_REGION_NTSC; } size_t LibretroHostInterface::retro_serialize_size() @@ -281,7 +281,7 @@ size_t LibretroHostInterface::retro_serialize_size() bool LibretroHostInterface::retro_serialize(void* data, size_t size) { std::unique_ptr stream = ByteStream_CreateMemoryStream(data, static_cast(size)); - if (!m_system->SaveState(stream.get(), 0)) + if (!System::SaveState(stream.get(), 0)) { Log_ErrorPrintf("Failed to save state to memory stream"); return false; @@ -293,7 +293,7 @@ bool LibretroHostInterface::retro_serialize(void* data, size_t size) bool LibretroHostInterface::retro_unserialize(const void* data, size_t size) { std::unique_ptr stream = ByteStream_CreateReadOnlyMemoryStream(data, static_cast(size)); - if (!m_system->LoadState(stream.get())) + if (!System::LoadState(stream.get())) { Log_ErrorPrintf("Failed to load save state from memory stream"); return false; @@ -307,7 +307,7 @@ void* LibretroHostInterface::retro_get_memory_data(unsigned id) switch (id) { case RETRO_MEMORY_SYSTEM_RAM: - return m_system ? m_system->GetBus()->GetRAM() : nullptr; + return System::IsShutdown() ? nullptr : Bus::g_ram; default: return nullptr; @@ -552,27 +552,27 @@ bool LibretroHostInterface::HasCoreVariablesChanged() void LibretroHostInterface::LoadSettings() { LibretroSettingsInterface si; - m_settings.Load(si); + g_settings.Load(si); // Assume BIOS files are located in system directory. const char* system_directory = nullptr; if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_directory) || !system_directory) system_directory = "bios"; - m_settings.bios_path = + g_settings.bios_path = StringUtil::StdStringFromFormat("%s%cscph1001.bin", system_directory, FS_OSPATH_SEPERATOR_CHARACTER); // Ensure we don't use the standalone memcard directory in shared mode. for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) - m_settings.memory_card_paths[i] = GetSharedMemoryCardPath(i); + g_settings.memory_card_paths[i] = GetSharedMemoryCardPath(i); } void LibretroHostInterface::UpdateSettings() { - Settings old_settings(std::move(m_settings)); + Settings old_settings(std::move(g_settings)); LoadSettings(); - if (m_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && - m_settings.gpu_renderer != GPURenderer::Software) + if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && + g_settings.gpu_renderer != GPURenderer::Software) { ReportMessage("Resolution changed, updating system AV info..."); @@ -588,14 +588,14 @@ void LibretroHostInterface::UpdateSettings() SwitchToHardwareRenderer(); // Don't let the base class mess with the GPU. - old_settings.gpu_resolution_scale = m_settings.gpu_resolution_scale; + old_settings.gpu_resolution_scale = g_settings.gpu_resolution_scale; } - if (m_settings.gpu_renderer != old_settings.gpu_renderer) + if (g_settings.gpu_renderer != old_settings.gpu_renderer) { ReportFormattedMessage("Switch to %s renderer pending, please restart the core to apply.", - Settings::GetRendererDisplayName(m_settings.gpu_renderer)); - m_settings.gpu_renderer = old_settings.gpu_renderer; + Settings::GetRendererDisplayName(g_settings.gpu_renderer)); + g_settings.gpu_renderer = old_settings.gpu_renderer; } CheckForSettingsChanges(old_settings); @@ -605,10 +605,10 @@ void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings { HostInterface::CheckForSettingsChanges(old_settings); - if (m_settings.display_aspect_ratio != old_settings.display_aspect_ratio) + if (g_settings.display_aspect_ratio != old_settings.display_aspect_ratio) UpdateGeometry(); - if (m_settings.log_level != old_settings.log_level) + if (g_settings.log_level != old_settings.log_level) UpdateLogging(); } @@ -618,7 +618,7 @@ void LibretroHostInterface::UpdateControllers() for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { - switch (m_settings.controller_types[i]) + switch (g_settings.controller_types[i]) { case ControllerType::None: break; @@ -633,7 +633,7 @@ void LibretroHostInterface::UpdateControllers() default: ReportFormattedError("Unhandled controller type '%s'.", - Settings::GetControllerTypeDisplayName(m_settings.controller_types[i])); + Settings::GetControllerTypeDisplayName(g_settings.controller_types[i])); break; } } @@ -641,7 +641,7 @@ void LibretroHostInterface::UpdateControllers() void LibretroHostInterface::UpdateControllersDigitalController(u32 index) { - DigitalController* controller = static_cast(m_system->GetController(index)); + DigitalController* controller = static_cast(System::GetController(index)); DebugAssert(controller); static constexpr std::array, 14> mapping = { @@ -669,7 +669,7 @@ void LibretroHostInterface::UpdateControllersDigitalController(u32 index) void LibretroHostInterface::UpdateControllersAnalogController(u32 index) { - AnalogController* controller = static_cast(m_system->GetController(index)); + AnalogController* controller = static_cast(System::GetController(index)); DebugAssert(controller); static constexpr std::array, 16> button_mapping = { @@ -875,8 +875,8 @@ void LibretroHostInterface::SwitchToHardwareRenderer() wi.surface_width = avi.geometry.base_width; wi.surface_height = avi.geometry.base_height; wi.surface_scale = 1.0f; - if (!display || !display->CreateRenderDevice(wi, {}, g_libretro_host_interface.m_settings.gpu_use_debug_device) || - !display->InitializeRenderDevice({}, m_settings.gpu_use_debug_device)) + if (!display || !display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device) || + !display->InitializeRenderDevice({}, g_settings.gpu_use_debug_device)) { Log_ErrorPrintf("Failed to create hardware host display"); return; @@ -884,7 +884,7 @@ void LibretroHostInterface::SwitchToHardwareRenderer() } std::swap(display, g_libretro_host_interface.m_display); - g_libretro_host_interface.m_system->RecreateGPU(renderer.value()); + System::RecreateGPU(renderer.value()); display->DestroyRenderDevice(); m_using_hardware_renderer = true; } @@ -917,13 +917,12 @@ void LibretroHostInterface::SwitchToSoftwareRenderer() } m_display = std::make_unique(); - m_system->RecreateGPU(GPURenderer::Software); + System::RecreateGPU(GPURenderer::Software); } bool LibretroHostInterface::DiskControlSetEjectState(bool ejected) { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlSetEjectState() - no system"); return false; @@ -933,51 +932,48 @@ bool LibretroHostInterface::DiskControlSetEjectState(bool ejected) if (ejected) { - if (!system->HasMedia()) + if (!System::HasMedia()) return false; - system->RemoveMedia(); + System::RemoveMedia(); return true; } else { const u32 image_to_insert = P_THIS->m_next_disc_index.value_or(0); Log_DevPrintf("Inserting image %u", image_to_insert); - return system->SwitchMediaFromPlaylist(image_to_insert); + return System::SwitchMediaFromPlaylist(image_to_insert); } } bool LibretroHostInterface::DiskControlGetEjectState() { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlGetEjectState() - no system"); return false; } - Log_DevPrintf("DiskControlGetEjectState() -> %u", static_cast(system->HasMedia())); - return system->HasMedia(); + Log_DevPrintf("DiskControlGetEjectState() -> %u", static_cast(System::HasMedia())); + return System::HasMedia(); } unsigned LibretroHostInterface::DiskControlGetImageIndex() { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlGetImageIndex() - no system"); return false; } - const u32 index = P_THIS->m_next_disc_index.value_or(system->GetMediaPlaylistIndex()); + const u32 index = P_THIS->m_next_disc_index.value_or(System::GetMediaPlaylistIndex()); Log_DevPrintf("DiskControlGetImageIndex() -> %u", index); return index; } bool LibretroHostInterface::DiskControlSetImageIndex(unsigned index) { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlSetImageIndex() - no system"); return false; @@ -985,7 +981,7 @@ bool LibretroHostInterface::DiskControlSetImageIndex(unsigned index) Log_DevPrintf("DiskControlSetImageIndex(%u)", index); - if (index >= system->GetMediaPlaylistCount()) + if (index >= System::GetMediaPlaylistCount()) return false; P_THIS->m_next_disc_index = index; @@ -994,21 +990,19 @@ bool LibretroHostInterface::DiskControlSetImageIndex(unsigned index) unsigned LibretroHostInterface::DiskControlGetNumImages() { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlGetNumImages() - no system"); return false; } - Log_DevPrintf("DiskControlGetNumImages() -> %u", system->GetMediaPlaylistCount()); - return static_cast(system->GetMediaPlaylistCount()); + Log_DevPrintf("DiskControlGetNumImages() -> %u", System::GetMediaPlaylistCount()); + return static_cast(System::GetMediaPlaylistCount()); } bool LibretroHostInterface::DiskControlReplaceImageIndex(unsigned index, const retro_game_info* info) { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlReplaceImageIndex() - no system"); return false; @@ -1016,22 +1010,21 @@ bool LibretroHostInterface::DiskControlReplaceImageIndex(unsigned index, const r Log_DevPrintf("DiskControlReplaceImageIndex(%u, %s)", index, info ? info->path : "null"); if (info && info->path) - return system->ReplaceMediaPathFromPlaylist(index, info->path); + return System::ReplaceMediaPathFromPlaylist(index, info->path); else - return system->RemoveMediaPathFromPlaylist(index); + return System::RemoveMediaPathFromPlaylist(index); } bool LibretroHostInterface::DiskControlAddImageIndex() { - System* system = P_THIS->GetSystem(); - if (!system) + if (System::IsShutdown()) { Log_ErrorPrintf("DiskControlAddImageIndex() - no system"); return false; } - Log_DevPrintf("DiskControlAddImageIndex() -> %zu", system->GetMediaPlaylistCount()); - system->AddMediaPathToPlaylist({}); + Log_DevPrintf("DiskControlAddImageIndex() -> %zu", System::GetMediaPlaylistCount()); + System::AddMediaPathToPlaylist({}); return true; } @@ -1044,11 +1037,10 @@ bool LibretroHostInterface::DiskControlSetInitialImage(unsigned index, const cha bool LibretroHostInterface::DiskControlGetImagePath(unsigned index, char* path, size_t len) { - System* system = P_THIS->GetSystem(); - if (!system || index >= system->GetMediaPlaylistCount()) + if (System::IsShutdown() || index >= System::GetMediaPlaylistCount()) return false; - const std::string& image_path = system->GetMediaPlaylistPath(index); + const std::string& image_path = System::GetMediaPlaylistPath(index); Log_DevPrintf("DiskControlGetImagePath(%u) -> %s", index, image_path.c_str()); if (image_path.empty()) return false; @@ -1059,11 +1051,10 @@ bool LibretroHostInterface::DiskControlGetImagePath(unsigned index, char* path, bool LibretroHostInterface::DiskControlGetImageLabel(unsigned index, char* label, size_t len) { - System* system = P_THIS->GetSystem(); - if (!system || index >= system->GetMediaPlaylistCount()) + if (System::IsShutdown() || index >= System::GetMediaPlaylistCount()) return false; - const std::string& image_path = system->GetMediaPlaylistPath(index); + const std::string& image_path = System::GetMediaPlaylistPath(index); if (image_path.empty()) return false; diff --git a/src/duckstation-libretro/libretro_host_interface.h b/src/duckstation-libretro/libretro_host_interface.h index 27a5cd8e0..a1d97a519 100644 --- a/src/duckstation-libretro/libretro_host_interface.h +++ b/src/duckstation-libretro/libretro_host_interface.h @@ -16,7 +16,7 @@ public: static bool HasCoreVariablesChanged(); static void InitDiskControlInterface(); - ALWAYS_INLINE u32 GetResolutionScale() const { return m_settings.gpu_resolution_scale; } + ALWAYS_INLINE u32 GetResolutionScale() const { return g_settings.gpu_resolution_scale; } bool Initialize() override; void Shutdown() override; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index b08b2d496..c03be4c66 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -269,7 +269,7 @@ void QtHostInterface::setDefaultSettings() return; } - Settings old_settings(std::move(m_settings)); + Settings old_settings(std::move(g_settings)); { std::lock_guard guard(m_settings_mutex); SetDefaultSettings(*m_settings_interface.get()); @@ -289,7 +289,7 @@ void QtHostInterface::applySettings() return; } - Settings old_settings(std::move(m_settings)); + Settings old_settings(std::move(g_settings)); { std::lock_guard guard(m_settings_mutex); CommonHostInterface::LoadSettings(*m_settings_interface.get()); @@ -298,7 +298,7 @@ void QtHostInterface::applySettings() CheckForSettingsChanges(old_settings); // detect when render-to-main flag changes - if (m_system) + if (!System::IsShutdown()) { const bool render_to_main = m_settings_interface->GetBoolValue("Main", "RenderToMainWindow", true); if (m_display && !m_is_fullscreen && render_to_main != m_is_rendering_to_main) @@ -422,7 +422,7 @@ void QtHostInterface::onHostDisplayWindowResized(int width, int height) m_display->ResizeRenderWindow(width, height); // re-render the display, since otherwise it will be out of date and stretched if paused - if (m_system) + if (!System::IsShutdown()) renderDisplay(); } @@ -434,7 +434,7 @@ void QtHostInterface::redrawDisplayWindow() return; } - if (!m_display || !m_system) + if (!m_display || System::IsShutdown()) return; renderDisplay(); @@ -458,8 +458,8 @@ bool QtHostInterface::AcquireHostDisplay() m_is_rendering_to_main = m_settings_interface->GetBoolValue("Main", "RenderToMainWindow", true); QtDisplayWidget* display_widget = - createDisplayRequested(m_worker_thread, QString::fromStdString(m_settings.gpu_adapter), - m_settings.gpu_use_debug_device, m_is_fullscreen, m_is_rendering_to_main); + createDisplayRequested(m_worker_thread, QString::fromStdString(g_settings.gpu_adapter), + g_settings.gpu_use_debug_device, m_is_fullscreen, m_is_rendering_to_main); if (!display_widget || !m_display->HasRenderDevice()) { emit destroyDisplayRequested(); @@ -470,7 +470,7 @@ bool QtHostInterface::AcquireHostDisplay() createImGuiContext(display_widget->devicePixelRatioFromScreen()); if (!m_display->MakeRenderContextCurrent() || - !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), m_settings.gpu_use_debug_device)) + !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device)) { destroyImGuiContext(); m_display->DestroyRenderDevice(); @@ -486,7 +486,7 @@ bool QtHostInterface::AcquireHostDisplay() HostDisplay* QtHostInterface::createHostDisplay() { - switch (m_settings.gpu_renderer) + switch (g_settings.gpu_renderer) { case GPURenderer::HardwareVulkan: m_display = std::make_unique(); @@ -630,20 +630,19 @@ void QtHostInterface::OnSystemPerformanceCountersUpdated() { HostInterface::OnSystemPerformanceCountersUpdated(); - DebugAssert(m_system); - emit systemPerformanceCountersUpdated(m_system->GetEmulationSpeed(), m_system->GetFPS(), m_system->GetVPS(), - m_system->GetAverageFrameTime(), m_system->GetWorstFrameTime()); + emit systemPerformanceCountersUpdated(System::GetEmulationSpeed(), System::GetFPS(), System::GetVPS(), + System::GetAverageFrameTime(), System::GetWorstFrameTime()); } void QtHostInterface::OnRunningGameChanged() { CommonHostInterface::OnRunningGameChanged(); - if (m_system) + if (!System::IsShutdown()) { - emit runningGameChanged(QString::fromStdString(m_system->GetRunningPath()), - QString::fromStdString(m_system->GetRunningCode()), - QString::fromStdString(m_system->GetRunningTitle())); + emit runningGameChanged(QString::fromStdString(System::GetRunningPath()), + QString::fromStdString(System::GetRunningCode()), + QString::fromStdString(System::GetRunningTitle())); } else { @@ -653,7 +652,7 @@ void QtHostInterface::OnRunningGameChanged() void QtHostInterface::OnSystemStateSaved(bool global, s32 slot) { - emit stateSaved(QString::fromStdString(m_system->GetRunningCode()), global, slot); + emit stateSaved(QString::fromStdString(System::GetRunningCode()), global, slot); } void QtHostInterface::LoadSettings() @@ -731,10 +730,10 @@ void QtHostInterface::powerOffSystem() return; } - if (!m_system) + if (System::IsShutdown()) return; - if (m_settings.save_state_on_exit) + if (g_settings.save_state_on_exit) SaveResumeSaveState(); DestroySystem(); @@ -756,7 +755,7 @@ void QtHostInterface::resetSystem() return; } - if (!m_system) + if (System::IsShutdown()) { Log_ErrorPrintf("resetSystem() called without system"); return; @@ -784,13 +783,13 @@ void QtHostInterface::changeDisc(const QString& new_disc_filename) return; } - if (!m_system) + if (System::IsShutdown()) return; if (!new_disc_filename.isEmpty()) - m_system->InsertMedia(new_disc_filename.toStdString().c_str()); + System::InsertMedia(new_disc_filename.toStdString().c_str()); else - m_system->RemoveMedia(); + System::RemoveMedia(); } static QString FormatTimestampForSaveStateMenu(u64 timestamp) @@ -925,7 +924,7 @@ void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done return; } - if (m_system) + if (!System::IsShutdown()) SaveState(global, slot); } @@ -940,7 +939,7 @@ void QtHostInterface::setAudioOutputVolume(int value) if (m_audio_stream) m_audio_stream->SetOutputVolume(value); - m_settings.audio_output_volume = value; + g_settings.audio_output_volume = value; } void QtHostInterface::setAudioOutputMuted(bool muted) @@ -952,9 +951,9 @@ void QtHostInterface::setAudioOutputMuted(bool muted) } if (m_audio_stream) - m_audio_stream->SetOutputVolume(muted ? 0 : m_settings.audio_output_volume); + m_audio_stream->SetOutputVolume(muted ? 0 : g_settings.audio_output_volume); - m_settings.audio_output_muted = muted; + g_settings.audio_output_muted = muted; } void QtHostInterface::startDumpingAudio() @@ -1058,14 +1057,14 @@ void QtHostInterface::threadEntryPoint() // TODO: Event which flags the thread as ready while (!m_shutdown_flag.load()) { - if (!m_system || m_paused) + if (!System::IsRunning()) { // wait until we have a system before running m_worker_thread_event_loop->exec(); continue; } - m_system->RunFrame(); + System::RunFrame(); UpdateControllerRumble(); if (m_frame_step_request) { @@ -1075,10 +1074,10 @@ void QtHostInterface::threadEntryPoint() renderDisplay(); - m_system->UpdatePerformanceCounters(); + System::UpdatePerformanceCounters(); if (m_speed_limiter_enabled) - m_system->Throttle(); + System::Throttle(); m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents); PollAndUpdate(); @@ -1095,14 +1094,14 @@ void QtHostInterface::threadEntryPoint() void QtHostInterface::renderDisplay() { - m_system->GetGPU()->ResetGraphicsAPIState(); + g_gpu->ResetGraphicsAPIState(); DrawImGuiWindows(); m_display->Render(); ImGui::NewFrame(); - m_system->GetGPU()->RestoreGraphicsAPIState(); + g_gpu->RestoreGraphicsAPIState(); } void QtHostInterface::wakeThread() diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 4710eaa58..cfb7d9a3d 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -108,7 +108,7 @@ bool SDLHostInterface::CreateDisplay() } std::unique_ptr display; - switch (m_settings.gpu_renderer) + switch (g_settings.gpu_renderer) { case GPURenderer::HardwareVulkan: display = std::make_unique(); @@ -130,8 +130,8 @@ bool SDLHostInterface::CreateDisplay() } Assert(display); - if (!display->CreateRenderDevice(wi.value(), m_settings.gpu_adapter, m_settings.gpu_use_debug_device) || - !display->InitializeRenderDevice(GetShaderCacheBasePath(), m_settings.gpu_use_debug_device)) + if (!display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device) || + !display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device)) { ReportError("Failed to create/initialize display render device"); return false; @@ -209,7 +209,7 @@ bool SDLHostInterface::AcquireHostDisplay() // Handle renderer switch if required. const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI(); bool needs_switch = false; - switch (m_settings.gpu_renderer) + switch (g_settings.gpu_renderer) { #ifdef WIN32 case GPURenderer::HardwareD3D11: @@ -321,8 +321,8 @@ void SDLHostInterface::OnRunningGameChanged() { CommonHostInterface::OnRunningGameChanged(); - if (m_system && !m_system->GetRunningTitle().empty()) - SDL_SetWindowTitle(m_window, m_system->GetRunningTitle().c_str()); + if (!System::GetRunningTitle().empty()) + SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str()); else SDL_SetWindowTitle(m_window, GetWindowTitle()); } @@ -345,7 +345,7 @@ void SDLHostInterface::SaveAndUpdateSettings() { m_settings_copy.Save(*m_settings_interface.get()); - Settings old_settings(std::move(m_settings)); + Settings old_settings(std::move(g_settings)); CommonHostInterface::LoadSettings(*m_settings_interface.get()); CheckForSettingsChanges(old_settings); @@ -451,7 +451,7 @@ void SDLHostInterface::LoadSettings() // Settings need to be loaded prior to creating the window for OpenGL bits. m_settings_interface = std::make_unique(GetSettingsFileName()); CommonHostInterface::LoadSettings(*m_settings_interface.get()); - m_settings_copy = m_settings; + m_settings_copy = g_settings; } void SDLHostInterface::ReportError(const char* message) @@ -604,7 +604,7 @@ void SDLHostInterface::DrawImGuiWindows() CommonHostInterface::DrawImGuiWindows(); - if (!m_system) + if (System::IsShutdown()) DrawPoweredOffWindow(); if (m_settings_window_open) @@ -621,7 +621,7 @@ void SDLHostInterface::DrawMainMenuBar() if (!ImGui::BeginMainMenuBar()) return; - const bool system_enabled = static_cast(m_system); + const bool system_enabled = static_cast(!System::IsShutdown()); if (ImGui::BeginMenu("System")) { @@ -653,9 +653,9 @@ void SDLHostInterface::DrawMainMenuBar() ClearImGuiFocus(); } - if (ImGui::MenuItem("Pause", nullptr, m_paused, system_enabled)) + if (ImGui::MenuItem("Pause", nullptr, System::IsPaused(), system_enabled)) { - RunLater([this]() { PauseSystem(!m_paused); }); + RunLater([this]() { PauseSystem(!System::IsPaused()); }); ClearImGuiFocus(); } @@ -669,7 +669,7 @@ void SDLHostInterface::DrawMainMenuBar() if (ImGui::MenuItem("Remove Disc", nullptr, false, system_enabled)) { - RunLater([this]() { m_system->RemoveMedia(); }); + RunLater([this]() { System::RemoveMedia(); }); ClearImGuiFocus(); } @@ -752,21 +752,26 @@ void SDLHostInterface::DrawMainMenuBar() ImGui::EndMenu(); } - if (m_system) + if (!System::IsShutdown()) { const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; - if (!m_paused) + if (System::IsPaused()) + { + ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (50.0f * framebuffer_scale)); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused"); + } + else { ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (420.0f * framebuffer_scale)); - ImGui::Text("Average: %.2fms", m_system->GetAverageFrameTime()); + ImGui::Text("Average: %.2fms", System::GetAverageFrameTime()); ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (310.0f * framebuffer_scale)); - ImGui::Text("Worst: %.2fms", m_system->GetWorstFrameTime()); + ImGui::Text("Worst: %.2fms", System::GetWorstFrameTime()); ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (210.0f * framebuffer_scale)); - const float speed = m_system->GetEmulationSpeed(); + const float speed = System::GetEmulationSpeed(); const u32 rounded_speed = static_cast(std::round(speed)); if (speed < 90.0f) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%u%%", rounded_speed); @@ -776,15 +781,10 @@ void SDLHostInterface::DrawMainMenuBar() ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%u%%", rounded_speed); ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (165.0f * framebuffer_scale)); - ImGui::Text("FPS: %.2f", m_system->GetFPS()); + ImGui::Text("FPS: %.2f", System::GetFPS()); ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (80.0f * framebuffer_scale)); - ImGui::Text("VPS: %.2f", m_system->GetVPS()); - } - else - { - ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (50.0f * framebuffer_scale)); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused"); + ImGui::Text("VPS: %.2f", System::GetVPS()); } } @@ -800,7 +800,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::BeginMenu("CPU Execution Mode")) { - const CPUExecutionMode current = m_settings.cpu_execution_mode; + const CPUExecutionMode current = g_settings.cpu_execution_mode; for (u32 i = 0; i < static_cast(CPUExecutionMode::Count); i++) { if (ImGui::MenuItem(Settings::GetCPUExecutionModeDisplayName(static_cast(i)), nullptr, @@ -868,7 +868,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() ImGui::Separator(); - if (ImGui::MenuItem("Dump Audio", nullptr, IsDumpingAudio(), HasSystem())) + if (ImGui::MenuItem("Dump Audio", nullptr, IsDumpingAudio(), System::IsValid())) { if (!IsDumpingAudio()) StartDumpingAudio(); @@ -885,7 +885,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() void SDLHostInterface::DrawDebugMenu() { - Settings::DebugSettings& debug_settings = m_settings.debugging; + Settings::DebugSettings& debug_settings = g_settings.debugging; bool settings_changed = false; if (ImGui::BeginMenu("Log Level")) @@ -893,7 +893,7 @@ void SDLHostInterface::DrawDebugMenu() for (u32 i = LOGLEVEL_NONE; i < LOGLEVEL_COUNT; i++) { if (ImGui::MenuItem(Settings::GetLogLevelDisplayName(static_cast(i)), nullptr, - m_settings.log_level == static_cast(i))) + g_settings.log_level == static_cast(i))) { m_settings_copy.log_level = static_cast(i); settings_changed = true; @@ -1455,7 +1455,7 @@ void SDLHostInterface::ClearImGuiFocus() void SDLHostInterface::DoStartDisc() { - Assert(!m_system); + Assert(System::IsShutdown()); nfdchar_t* path = nullptr; if (!NFD_OpenDialog("bin,img,cue,chd,exe,psexe,psf", nullptr, &path) || !path || std::strlen(path) == 0) @@ -1470,18 +1470,18 @@ void SDLHostInterface::DoStartDisc() void SDLHostInterface::DoChangeDisc() { - Assert(m_system); + Assert(!System::IsShutdown()); nfdchar_t* path = nullptr; if (!NFD_OpenDialog("bin,img,cue,chd", nullptr, &path) || !path || std::strlen(path) == 0) return; - if (m_system->InsertMedia(path)) + if (System::InsertMedia(path)) AddFormattedOSDMessage(2.0f, "Switched CD to '%s'", path); else AddOSDMessage("Failed to switch CD. The log may contain further information."); - m_system->ResetPerformanceCounters(); + System::ResetPerformanceCounters(); } void SDLHostInterface::Run() @@ -1490,9 +1490,9 @@ void SDLHostInterface::Run() { PollAndUpdate(); - if (m_system && !m_paused) + if (System::IsRunning()) { - m_system->RunFrame(); + System::RunFrame(); UpdateControllerRumble(); if (m_frame_step_request) { @@ -1505,28 +1505,28 @@ void SDLHostInterface::Run() { DrawImGuiWindows(); - if (m_system) - m_system->GetGPU()->ResetGraphicsAPIState(); + if (System::IsRunning()) + g_gpu->ResetGraphicsAPIState(); m_display->Render(); ImGui_ImplSDL2_NewFrame(m_window); ImGui::NewFrame(); - if (m_system) + if (System::IsRunning()) { - m_system->GetGPU()->RestoreGraphicsAPIState(); - m_system->UpdatePerformanceCounters(); + g_gpu->RestoreGraphicsAPIState(); + System::UpdatePerformanceCounters(); if (m_speed_limiter_enabled) - m_system->Throttle(); + System::Throttle(); } } } // Save state on exit so it can be resumed - if (m_system) + if (!System::IsShutdown()) { - if (m_settings.save_state_on_exit) + if (g_settings.save_state_on_exit) SaveResumeSaveState(); DestroySystem(); } diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h index 64dabeac4..de98db5bd 100644 --- a/src/duckstation-sdl/sdl_host_interface.h +++ b/src/duckstation-sdl/sdl_host_interface.h @@ -12,7 +12,6 @@ #include #include -class System; class AudioStream; class INISettingsInterface; @@ -61,8 +60,6 @@ protected: void UpdateInputMap() override; private: - bool HasSystem() const { return static_cast(m_system); } - bool CreateSDLWindow(); void DestroySDLWindow(); bool CreateDisplay(); diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 16b7fbcc6..fb2572904 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -61,9 +61,9 @@ bool CommonHostInterface::Initialize() Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str()); LoadSettings(); - UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), - m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, - m_settings.log_to_file); + UpdateLogSettings(g_settings.log_level, g_settings.log_filter.empty() ? nullptr : g_settings.log_filter.c_str(), + g_settings.log_to_console, g_settings.log_to_debug, g_settings.log_to_window, + g_settings.log_to_file); m_game_list = std::make_unique(); m_game_list->SetCacheFilename(GetUserDirectoryRelativePath("cache/gamelist.cache")); @@ -102,7 +102,7 @@ void CommonHostInterface::Shutdown() ShutdownDiscordPresence(); #endif - m_system.reset(); + System::Shutdown(); m_audio_stream.reset(); if (m_display) ReleaseHostDisplay(); @@ -155,13 +155,13 @@ bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters) } // enter fullscreen if requested in the parameters - if (!m_settings.start_paused && ((parameters.override_fullscreen.has_value() && *parameters.override_fullscreen) || - (!parameters.override_fullscreen.has_value() && m_settings.start_fullscreen))) + if (!g_settings.start_paused && ((parameters.override_fullscreen.has_value() && *parameters.override_fullscreen) || + (!parameters.override_fullscreen.has_value() && g_settings.start_fullscreen))) { SetFullscreen(true); } - if (m_settings.audio_dump_on_boot) + if (g_settings.audio_dump_on_boot) StartDumpingAudio(); UpdateSpeedLimiterState(); @@ -170,30 +170,28 @@ bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters) void CommonHostInterface::PauseSystem(bool paused) { - if (paused == m_paused || !m_system) + if (paused == System::IsPaused() || System::IsShutdown()) return; - m_paused = paused; - m_audio_stream->PauseOutput(m_paused); + System::SetState(paused ? System::State::Paused : System::State::Running); + m_audio_stream->PauseOutput(paused); OnSystemPaused(paused); UpdateSpeedLimiterState(); if (!paused) - m_system->ResetPerformanceCounters(); + System::ResetPerformanceCounters(); } void CommonHostInterface::DestroySystem() { SetTimerResolutionIncreased(false); - m_paused = false; - HostInterface::DestroySystem(); } void CommonHostInterface::PowerOffSystem() { - if (m_settings.save_state_on_exit) + if (g_settings.save_state_on_exit) SaveResumeSaveState(); HostInterface::PowerOffSystem(); @@ -469,20 +467,20 @@ std::unique_ptr CommonHostInterface::CreateControllerInterf bool CommonHostInterface::LoadState(bool global, s32 slot) { - if (!global && (!m_system || m_system->GetRunningCode().empty())) + if (!global && (System::IsShutdown() || System::GetRunningCode().empty())) { ReportFormattedError("Can't save per-game state without a running game code."); return false; } std::string save_path = - global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), slot); + global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(System::GetRunningCode().c_str(), slot); return LoadState(save_path.c_str()); } bool CommonHostInterface::SaveState(bool global, s32 slot) { - const std::string& code = m_system->GetRunningCode(); + const std::string& code = System::GetRunningCode(); if (!global && code.empty()) { ReportFormattedError("Can't save per-game state without a running game code."); @@ -504,7 +502,7 @@ bool CommonHostInterface::ResumeSystemFromState(const char* filename, bool boot_ if (!BootSystem(boot_params)) return false; - const bool global = m_system->GetRunningCode().empty(); + const bool global = System::GetRunningCode().empty(); if (global) { ReportFormattedError("Cannot resume system with undetectable game code from '%s'.", filename); @@ -516,7 +514,7 @@ bool CommonHostInterface::ResumeSystemFromState(const char* filename, bool boot_ } else { - const std::string path = GetGameSaveStateFileName(m_system->GetRunningCode().c_str(), -1); + const std::string path = GetGameSaveStateFileName(System::GetRunningCode().c_str(), -1); if (FileSystem::FileExists(path.c_str())) { if (!LoadState(path.c_str()) && !boot_on_failure) @@ -527,8 +525,8 @@ bool CommonHostInterface::ResumeSystemFromState(const char* filename, bool boot_ } else if (!boot_on_failure) { - ReportFormattedError("Resume save state not found for '%s' ('%s').", m_system->GetRunningCode().c_str(), - m_system->GetRunningTitle().c_str()); + ReportFormattedError("Resume save state not found for '%s' ('%s').", System::GetRunningCode().c_str(), + System::GetRunningTitle().c_str()); DestroySystem(); return false; } @@ -551,34 +549,36 @@ bool CommonHostInterface::ResumeSystemFromMostRecentState() void CommonHostInterface::UpdateSpeedLimiterState() { - if (!m_system || !m_audio_stream || !m_display) - return; + m_speed_limiter_enabled = g_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; - m_speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; - - const bool is_non_standard_speed = (std::abs(m_settings.emulation_speed - 1.0f) > 0.05f); + const bool is_non_standard_speed = (std::abs(g_settings.emulation_speed - 1.0f) > 0.05f); const bool audio_sync_enabled = - m_paused || (m_speed_limiter_enabled && m_settings.audio_sync_enabled && !is_non_standard_speed); + !System::IsRunning() || (m_speed_limiter_enabled && g_settings.audio_sync_enabled && !is_non_standard_speed); const bool video_sync_enabled = - m_paused || (m_speed_limiter_enabled && m_settings.video_sync_enabled && !is_non_standard_speed); + !System::IsRunning() || (m_speed_limiter_enabled && g_settings.video_sync_enabled && !is_non_standard_speed); Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", (audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : "")); - m_audio_stream->SetSync(audio_sync_enabled); - if (audio_sync_enabled) - m_audio_stream->EmptyBuffers(); + if (m_audio_stream) + { + m_audio_stream->SetSync(audio_sync_enabled); + if (audio_sync_enabled) + m_audio_stream->EmptyBuffers(); + } - m_display->SetVSync(video_sync_enabled); + if (m_display) + m_display->SetVSync(video_sync_enabled); - if (m_settings.increase_timer_resolution) + if (g_settings.increase_timer_resolution) SetTimerResolutionIncreased(m_speed_limiter_enabled); - m_system->ResetPerformanceCounters(); + if (System::IsValid()) + System::ResetPerformanceCounters(); } void CommonHostInterface::RecreateSystem() { - const bool was_paused = m_paused; + const bool was_paused = System::IsPaused(); HostInterface::RecreateSystem(); if (was_paused) PauseSystem(true); @@ -588,12 +588,12 @@ void CommonHostInterface::UpdateLogSettings(LOGLEVEL level, const char* filter, bool log_to_window, bool log_to_file) { Log::SetFilterLevel(level); - Log::SetConsoleOutputParams(m_settings.log_to_console, filter, level); - Log::SetDebugOutputParams(m_settings.log_to_debug, filter, level); + Log::SetConsoleOutputParams(g_settings.log_to_console, filter, level); + Log::SetDebugOutputParams(g_settings.log_to_debug, filter, level); if (log_to_file) { - Log::SetFileOutputParams(m_settings.log_to_file, GetUserDirectoryRelativePath("duckstation.log").c_str(), true, + Log::SetFileOutputParams(g_settings.log_to_file, GetUserDirectoryRelativePath("duckstation.log").c_str(), true, filter, level); } else @@ -707,7 +707,7 @@ void CommonHostInterface::OnControllerTypeChanged(u32 slot) void CommonHostInterface::DrawImGuiWindows() { - if (m_system) + if (System::IsValid()) { DrawDebugWindows(); DrawFPSWindow(); @@ -721,7 +721,7 @@ void CommonHostInterface::DrawImGuiWindows() void CommonHostInterface::DrawFPSWindow() { - if (!(m_settings.display_show_fps | m_settings.display_show_vps | m_settings.display_show_speed)) + if (!(g_settings.display_show_fps | g_settings.display_show_vps | g_settings.display_show_speed)) return; const ImVec2 window_size = @@ -739,12 +739,12 @@ void CommonHostInterface::DrawFPSWindow() } bool first = true; - if (m_settings.display_show_fps) + if (g_settings.display_show_fps) { - ImGui::Text("%.2f", m_system->GetFPS()); + ImGui::Text("%.2f", System::GetFPS()); first = false; } - if (m_settings.display_show_vps) + if (g_settings.display_show_vps) { if (first) { @@ -757,9 +757,9 @@ void CommonHostInterface::DrawFPSWindow() ImGui::SameLine(); } - ImGui::Text("%.2f", m_system->GetVPS()); + ImGui::Text("%.2f", System::GetVPS()); } - if (m_settings.display_show_speed) + if (g_settings.display_show_speed) { if (first) { @@ -772,7 +772,7 @@ void CommonHostInterface::DrawFPSWindow() ImGui::SameLine(); } - const float speed = m_system->GetEmulationSpeed(); + const float speed = System::GetEmulationSpeed(); const u32 rounded_speed = static_cast(std::round(speed)); if (speed < 90.0f) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%u%%", rounded_speed); @@ -823,7 +823,7 @@ void CommonHostInterface::DrawOSDMessages() continue; } - if (!m_settings.display_show_osd_messages) + if (!g_settings.display_show_osd_messages) continue; const float opacity = std::min(time_remaining, 1.0f); @@ -848,28 +848,25 @@ void CommonHostInterface::DrawOSDMessages() void CommonHostInterface::DrawDebugWindows() { - const Settings::DebugSettings& debug_settings = m_system->GetSettings().debugging; - - if (debug_settings.show_gpu_state) - m_system->GetGPU()->DrawDebugStateWindow(); - if (debug_settings.show_cdrom_state) - m_system->GetCDROM()->DrawDebugWindow(); - if (debug_settings.show_timers_state) - m_system->GetTimers()->DrawDebugStateWindow(); - if (debug_settings.show_spu_state) - m_system->GetSPU()->DrawDebugStateWindow(); - if (debug_settings.show_mdec_state) - m_system->GetMDEC()->DrawDebugStateWindow(); + if (g_settings.debugging.show_gpu_state) + g_gpu->DrawDebugStateWindow(); + if (g_settings.debugging.show_cdrom_state) + g_cdrom.DrawDebugWindow(); + if (g_settings.debugging.show_timers_state) + g_timers.DrawDebugStateWindow(); + if (g_settings.debugging.show_spu_state) + g_spu.DrawDebugStateWindow(); + if (g_settings.debugging.show_mdec_state) + g_mdec.DrawDebugStateWindow(); } void CommonHostInterface::DoFrameStep() { - if (!m_system) + if (System::IsShutdown()) return; m_frame_step_request = true; - if (m_paused) - PauseSystem(false); + PauseSystem(false); } std::optional @@ -926,11 +923,9 @@ void CommonHostInterface::AddControllerRumble(u32 controller_index, u32 num_moto void CommonHostInterface::UpdateControllerRumble() { - DebugAssert(m_system); - for (ControllerRumbleState& rumble : m_controller_vibration_motors) { - Controller* controller = m_system->GetController(rumble.controller_index); + Controller* controller = System::GetController(rumble.controller_index); if (!controller) continue; @@ -984,7 +979,7 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) for (u32 controller_index = 0; controller_index < 2; controller_index++) { - const ControllerType ctype = m_settings.controller_types[controller_index]; + const ControllerType ctype = g_settings.controller_types[controller_index]; if (ctype == ControllerType::None) continue; @@ -1004,10 +999,10 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) continue; AddButtonToInputMap(binding, device, button, [this, controller_index, button_code](bool pressed) { - if (!m_system) + if (System::IsShutdown()) return; - Controller* controller = m_system->GetController(controller_index); + Controller* controller = System::GetController(controller_index); if (controller) controller->SetButtonState(button_code, pressed); }); @@ -1029,10 +1024,10 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) continue; AddAxisToInputMap(binding, device, axis, [this, controller_index, axis_code](float value) { - if (!m_system) + if (System::IsShutdown()) return; - Controller* controller = m_system->GetController(controller_index); + Controller* controller = System::GetController(controller_index); if (controller) controller->SetAxisState(axis_code, value); }); @@ -1253,23 +1248,23 @@ void CommonHostInterface::RegisterGeneralHotkeys() RegisterHotkey(StaticString("General"), StaticString("TogglePause"), StaticString("Toggle Pause"), [this](bool pressed) { - if (!pressed) - PauseSystem(!m_paused); + if (System::IsValid() && !pressed) + PauseSystem(!System::IsPaused()); }); RegisterHotkey(StaticString("General"), StaticString("PowerOff"), StaticString("Power Off System"), [this](bool pressed) { - if (!pressed && m_system) + if (!pressed && System::IsValid()) { - if (m_settings.confim_power_off && !m_batch_mode) + if (g_settings.confim_power_off && !m_batch_mode) { SmallString confirmation_message("Are you sure you want to stop emulation?"); - if (m_settings.save_state_on_exit) + if (g_settings.save_state_on_exit) confirmation_message.AppendString("\n\nThe current state will be saved."); if (!ConfirmMessage(confirmation_message)) { - m_system->ResetPerformanceCounters(); + System::ResetPerformanceCounters(); return; } } @@ -1280,7 +1275,7 @@ void CommonHostInterface::RegisterGeneralHotkeys() RegisterHotkey(StaticString("General"), StaticString("Screenshot"), StaticString("Save Screenshot"), [this](bool pressed) { - if (!pressed && m_system) + if (!pressed && System::IsValid()) SaveScreenshot(); }); @@ -1363,33 +1358,33 @@ void CommonHostInterface::RegisterSaveStateHotkeys() void CommonHostInterface::RegisterAudioHotkeys() { RegisterHotkey(StaticString("Audio"), StaticString("AudioMute"), StaticString("Toggle Mute"), [this](bool pressed) { - if (m_system && !pressed) + if (System::IsValid() && !pressed) { - m_settings.audio_output_muted = !m_settings.audio_output_muted; - m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume); - if (m_settings.audio_output_muted) + g_settings.audio_output_muted = !g_settings.audio_output_muted; + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); + if (g_settings.audio_output_muted) AddOSDMessage("Volume: Muted", 2.0f); else - AddFormattedOSDMessage(2.0f, "Volume: %d%%", m_settings.audio_output_volume); + AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); } }); RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeUp"), StaticString("Volume Up"), [this](bool pressed) { - if (m_system && pressed) + if (System::IsValid() && pressed) { - m_settings.audio_output_volume = std::min(m_settings.audio_output_volume + 10, 100); - m_settings.audio_output_muted = false; - m_audio_stream->SetOutputVolume(m_settings.audio_output_volume); - AddFormattedOSDMessage(2.0f, "Volume: %d%%", m_settings.audio_output_volume); + g_settings.audio_output_volume = std::min(g_settings.audio_output_volume + 10, 100); + g_settings.audio_output_muted = false; + m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); + AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); } }); RegisterHotkey(StaticString("Audio"), StaticString("AudioVolumeDown"), StaticString("Volume Down"), [this](bool pressed) { - if (m_system && pressed) + if (System::IsValid() && pressed) { - m_settings.audio_output_volume = std::max(m_settings.audio_output_volume - 10, 0); - m_settings.audio_output_muted = false; - m_audio_stream->SetOutputVolume(m_settings.audio_output_volume); - AddFormattedOSDMessage(2.0f, "Volume: %d%%", m_settings.audio_output_volume); + g_settings.audio_output_volume = std::max(g_settings.audio_output_volume - 10, 0); + g_settings.audio_output_muted = false; + m_audio_stream->SetOutputVolume(g_settings.audio_output_volume); + AddFormattedOSDMessage(2.0f, "Volume: %d%%", g_settings.audio_output_volume); } }); } @@ -1443,7 +1438,7 @@ void CommonHostInterface::ClearAllControllerBindings(SettingsInterface& si) { for (u32 controller_index = 1; controller_index <= NUM_CONTROLLER_AND_CARD_PORTS; controller_index++) { - const ControllerType ctype = m_settings.controller_types[controller_index - 1]; + const ControllerType ctype = g_settings.controller_types[controller_index - 1]; if (ctype == ControllerType::None) continue; @@ -1483,7 +1478,7 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn return; } - m_settings.controller_types[controller_index - 1] = *ctype; + g_settings.controller_types[controller_index - 1] = *ctype; HostInterface::OnControllerTypeChanged(controller_index - 1); si.SetStringValue(section_name, "Type", Settings::GetControllerTypeName(*ctype)); @@ -1520,8 +1515,8 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn } } - if (m_system) - m_system->UpdateControllers(); + if (System::IsValid()) + System::UpdateControllers(); UpdateInputMap(si); @@ -1540,7 +1535,7 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt for (u32 controller_index = 1; controller_index <= NUM_CONTROLLER_AND_CARD_PORTS; controller_index++) { - const ControllerType ctype = m_settings.controller_types[controller_index - 1]; + const ControllerType ctype = g_settings.controller_types[controller_index - 1]; if (ctype == ControllerType::None) continue; @@ -1802,32 +1797,27 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings) { HostInterface::CheckForSettingsChanges(old_settings); - if (m_system) + if (System::IsValid()) { - if (m_settings.audio_backend != old_settings.audio_backend || - m_settings.audio_buffer_size != old_settings.audio_buffer_size) - { - m_audio_stream->PauseOutput(m_paused); - UpdateSpeedLimiterState(); - } - - if (m_settings.video_sync_enabled != old_settings.video_sync_enabled || - m_settings.audio_sync_enabled != old_settings.audio_sync_enabled || - m_settings.speed_limiter_enabled != old_settings.speed_limiter_enabled || - m_settings.increase_timer_resolution != old_settings.increase_timer_resolution || - m_settings.emulation_speed != old_settings.emulation_speed) + if (g_settings.audio_backend != old_settings.audio_backend || + g_settings.audio_buffer_size != old_settings.audio_buffer_size || + g_settings.video_sync_enabled != old_settings.video_sync_enabled || + g_settings.audio_sync_enabled != old_settings.audio_sync_enabled || + g_settings.speed_limiter_enabled != old_settings.speed_limiter_enabled || + g_settings.increase_timer_resolution != old_settings.increase_timer_resolution || + g_settings.emulation_speed != old_settings.emulation_speed) { UpdateSpeedLimiterState(); } } - if (m_settings.log_level != old_settings.log_level || m_settings.log_filter != old_settings.log_filter || - m_settings.log_to_console != old_settings.log_to_console || - m_settings.log_to_window != old_settings.log_to_window || m_settings.log_to_file != old_settings.log_to_file) + if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter || + g_settings.log_to_console != old_settings.log_to_console || + g_settings.log_to_window != old_settings.log_to_window || g_settings.log_to_file != old_settings.log_to_file) { - UpdateLogSettings(m_settings.log_level, m_settings.log_filter.empty() ? nullptr : m_settings.log_filter.c_str(), - m_settings.log_to_console, m_settings.log_to_debug, m_settings.log_to_window, - m_settings.log_to_file); + UpdateLogSettings(g_settings.log_level, g_settings.log_filter.empty() ? nullptr : g_settings.log_filter.c_str(), + g_settings.log_to_console, g_settings.log_to_debug, g_settings.log_to_window, + g_settings.log_to_file); } UpdateInputMap(); @@ -1912,27 +1902,27 @@ void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::str bool CommonHostInterface::SaveResumeSaveState() { - if (!m_system) + if (System::IsShutdown()) return false; - const bool global = m_system->GetRunningCode().empty(); + const bool global = System::GetRunningCode().empty(); return SaveState(global, -1); } bool CommonHostInterface::IsDumpingAudio() const { - return m_system ? m_system->GetSPU()->IsDumpingAudio() : false; + return g_spu.IsDumpingAudio(); } bool CommonHostInterface::StartDumpingAudio(const char* filename) { - if (!m_system) + if (System::IsShutdown()) return false; std::string auto_filename; if (!filename) { - const auto& code = m_system->GetRunningCode(); + const auto& code = System::GetRunningCode(); if (code.empty()) { auto_filename = GetUserDirectoryRelativePath("dump/audio/%s.wav", GetTimestampStringForFileName().GetCharArray()); @@ -1946,7 +1936,7 @@ bool CommonHostInterface::StartDumpingAudio(const char* filename) filename = auto_filename.c_str(); } - if (m_system->GetSPU()->StartDumpingAudio(filename)) + if (g_spu.StartDumpingAudio(filename)) { AddFormattedOSDMessage(5.0f, "Started dumping audio to '%s'.", filename); return true; @@ -1960,7 +1950,7 @@ bool CommonHostInterface::StartDumpingAudio(const char* filename) void CommonHostInterface::StopDumpingAudio() { - if (!m_system || !m_system->GetSPU()->StopDumpingAudio()) + if (System::IsShutdown() || !g_spu.StopDumpingAudio()) return; AddOSDMessage("Stopped dumping audio.", 5.0f); @@ -1969,13 +1959,13 @@ void CommonHostInterface::StopDumpingAudio() bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, bool full_resolution /* = true */, bool apply_aspect_ratio /* = true */) { - if (!m_system) + if (System::IsShutdown()) return false; std::string auto_filename; if (!filename) { - const auto& code = m_system->GetRunningCode(); + const auto& code = System::GetRunningCode(); const char* extension = "png"; if (code.empty()) { @@ -1991,9 +1981,9 @@ bool CommonHostInterface::SaveScreenshot(const char* filename /* = nullptr */, b filename = auto_filename.c_str(); } - m_system->GetGPU()->ResetGraphicsAPIState(); + g_gpu->ResetGraphicsAPIState(); const bool screenshot_saved = m_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio); - m_system->GetGPU()->RestoreGraphicsAPIState(); + g_gpu->RestoreGraphicsAPIState(); if (!screenshot_saved) { AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'", filename); @@ -2052,10 +2042,10 @@ void CommonHostInterface::UpdateDiscordPresence() rp.startTimestamp = std::time(nullptr); SmallString details_string; - if (m_system) + if (System::IsValid()) { - details_string.AppendFormattedString("%s (%s)", m_system->GetRunningTitle().c_str(), - m_system->GetRunningCode().c_str()); + details_string.AppendFormattedString("%s (%s)", System::GetRunningTitle().c_str(), + System::GetRunningCode().c_str()); } else { diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 06577f8a6..16be28a6e 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -280,7 +280,6 @@ protected: std::deque m_osd_messages; std::mutex m_osd_messages_lock; - bool m_paused = false; bool m_frame_step_request = false; bool m_speed_limiter_temp_disabled = false; bool m_speed_limiter_enabled = false; diff --git a/src/frontend-common/controller_interface.cpp b/src/frontend-common/controller_interface.cpp index 43f4a170f..8816454f1 100644 --- a/src/frontend-common/controller_interface.cpp +++ b/src/frontend-common/controller_interface.cpp @@ -21,17 +21,6 @@ void ControllerInterface::Shutdown() m_host_interface = nullptr; } -System* ControllerInterface::GetSystem() const -{ - return m_host_interface->GetSystem(); -} - -Controller* ControllerInterface::GetController(u32 slot) const -{ - System* system = GetSystem(); - return system ? system->GetController(slot) : nullptr; -} - void ControllerInterface::SetHook(Hook::Callback callback) { std::unique_lock lock(m_event_intercept_mutex); diff --git a/src/frontend-common/controller_interface.h b/src/frontend-common/controller_interface.h index 906a27d56..6f9af9247 100644 --- a/src/frontend-common/controller_interface.h +++ b/src/frontend-common/controller_interface.h @@ -7,7 +7,6 @@ #include class HostInterface; -class System; class Controller; class ControllerInterface @@ -75,8 +74,6 @@ public: void ClearHook(); protected: - System* GetSystem() const; - Controller* GetController(u32 slot) const; bool DoEventHook(Hook::Type type, int controller_index, int button_or_axis_number, float value); void OnControllerConnected(int host_id); diff --git a/src/frontend-common/save_state_selector_ui.cpp b/src/frontend-common/save_state_selector_ui.cpp index 5da1f6e4b..bf63044e9 100644 --- a/src/frontend-common/save_state_selector_ui.cpp +++ b/src/frontend-common/save_state_selector_ui.cpp @@ -42,13 +42,12 @@ void SaveStateSelectorUI::RefreshList() { ClearList(); - const System* system = m_host_interface->GetSystem(); - if (system && !system->GetRunningCode().empty()) + if (!System::GetRunningCode().empty()) { for (s32 i = 1; i <= CommonHostInterface::GLOBAL_SAVE_STATE_SLOTS; i++) { std::optional ssi = - m_host_interface->GetExtendedSaveStateInfo(system->GetRunningCode().c_str(), i); + m_host_interface->GetExtendedSaveStateInfo(System::GetRunningCode().c_str(), i); ListEntry li; if (ssi)