From 2f01416d219c3fb5f3ee1776cc78be6ea370ddd6 Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 14 Apr 2026 11:41:59 +0200 Subject: [PATCH] It's definitely possible, just that the interpreter doesn't run often enough... --- src/backend/Core.cpp | 96 +++++++++++++++++++++--------------- src/backend/Core.hpp | 5 ++ src/backend/Scheduler.cpp | 12 +++-- src/backend/Scheduler.hpp | 13 +++-- src/backend/core/mmio/VI.cpp | 17 ++++--- src/backend/core/mmio/VI.hpp | 5 +- src/frontend/EmuThread.cpp | 18 +++---- src/frontend/EmuThread.hpp | 3 -- src/frontend/KaizenGui.cpp | 24 +++++---- 9 files changed, 113 insertions(+), 80 deletions(-) diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index e3edd5e..5814ed9 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -3,6 +3,8 @@ #include #include +using namespace std::chrono_literals; + namespace n64 { Core::Core() { const auto selectedCpu = Options::GetInstance().GetValue("cpu", "type"); @@ -28,8 +30,10 @@ void Core::Stop() { } void Core::Reset() { + Scheduler::GetInstance().Reset(); regs.Reset(); mem->Reset(); + cpu->Reset(); if(romLoaded) mem->mmio.si.pif.Execute(); @@ -88,49 +92,59 @@ void Core::StepRSP(const u32 cpuCycles) { } } +void Core::VIHalflineComplete(u64 time, float volumeL, float volumeR) { + MMIO& mmio = mem->mmio; + VI& vi = mmio.vi; + AI& ai = mmio.ai; + MI& mi = mmio.mi; + + s64 taken = time - vi.lastHalflineTime; + vi.lastHalflineTime = time; + + u64 overshot; + + do { + overshot = taken - vi.cyclesPerHalfline; + vi.halfline++; + + if(vi.halfline > vi.numHalflines) { + vi.halfline = 0; + vi.field++; + + ai.Step(vi.missingCycles, volumeL, volumeR); + { + std::lock_guard lk(mtxPresentationReady); + readyForPresentation.notify_all(); + } + { + std::unique_lock lk(mtxPresentationFinished); + finishedPresentation.wait_for(lk, 1ms); + } + //parallel.UpdateScreen(); + } + + if(vi.field > vi.numFields) + vi.field = 0; + + vi.current = (vi.halfline << 1) + vi.field; + + if ((vi.current & 0x3FE) == vi.intr) { + mi.InterruptRaise(MI::Interrupt::VI); + } + + taken -= vi.cyclesPerHalfline; + } while(taken > vi.cyclesPerHalfline); + + u64 next = vi.cyclesPerHalfline - overshot; + Scheduler::GetInstance().EnqueueRelative(next, VI_HALFLINE); +} + void Core::Run(const float volumeL, const float volumeR) { MMIO &mmio = mem->mmio; + + const u32 taken = StepCPU(); + cycles += taken; - bool broken = false; - for (int field = 0; field < mmio.vi.numFields; field++) { - u32 frameCycles = 0; - for (int i = 0; i < mmio.vi.numHalflines; i++) { - mmio.vi.current = (i << 1) + field; - - if ((mmio.vi.current & 0x3FE) == mmio.vi.intr) { - mmio.mi.InterruptRaise(MI::Interrupt::VI); - } - - while(cycles < mem->mmio.vi.cyclesPerHalfline) { - const u32 taken = StepCPU(); - cycles += taken; - - if((broken = breakpoints.contains(regs.nextPC))) - break; - - StepRSP(taken); - frameCycles += taken; - Scheduler::GetInstance().Tick(taken); - } - - if(broken) - break; - - cycles -= mmio.vi.cyclesPerHalfline; - } - - if(broken) - break; - - if ((mmio.vi.current & 0x3FE) == mmio.vi.intr) { - mmio.mi.InterruptRaise(MI::Interrupt::VI); - } - - mmio.ai.Step(frameCycles, volumeL, volumeR); - Scheduler::GetInstance().Tick(frameCycles); - } - - if(broken) - pause = true; + StepRSP(taken); } } // namespace n64 diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index 79113c2..6593fbe 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -29,6 +29,8 @@ struct Core { return *GetInstance().mem; } + void VIHalflineComplete(u64 time, float, float); + u32 StepCPU(); void StepRSP(u32 cpuCycles); void Stop(); @@ -56,6 +58,9 @@ struct Core { std::unique_ptr mem = std::make_unique(); std::unique_ptr cpu; + std::mutex mtxPresentationFinished, mtxPresentationReady; + std::condition_variable readyForPresentation, finishedPresentation; + Registers regs; ParallelRDP parallel; }; diff --git a/src/backend/Scheduler.cpp b/src/backend/Scheduler.cpp index d031a0d..ee7631c 100644 --- a/src/backend/Scheduler.cpp +++ b/src/backend/Scheduler.cpp @@ -18,15 +18,15 @@ u64 Scheduler::Remove(const EventType eventType) const { return 0; } -void Scheduler::Tick(const u64 t) { +void Scheduler::Tick(const u64 t, float volumeL, float volumeR) { n64::Mem& mem = n64::Core::GetMem(); ticks += t; n64::MI &mi = mem.mmio.mi; n64::SI &si = mem.mmio.si; n64::PI &pi = mem.mmio.pi; - while (ticks >= events.top().time) { - switch (const auto type = events.top().type) { + for (auto event = events.top(); ticks >= event.time; events.pop()) { + switch (event.type) { case SI_DMA: si.DMA(); break; @@ -37,15 +37,17 @@ void Scheduler::Tick(const u64 t) { case PI_BUS_WRITE_COMPLETE: pi.ioBusy = false; break; + case VI_HALFLINE: + n64::Core::GetInstance().VIHalflineComplete(ticks, volumeL, volumeR); + break; case NONE: break; case IMPOSSIBLE: Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness"); return; default: - Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unknown scheduler event type {}", static_cast(type)); + Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unknown scheduler event type {}", static_cast(event.type)); return; } - events.pop(); } } diff --git a/src/backend/Scheduler.hpp b/src/backend/Scheduler.hpp index 5a37a52..f1e21ae 100644 --- a/src/backend/Scheduler.hpp +++ b/src/backend/Scheduler.hpp @@ -3,7 +3,7 @@ #include #include -enum EventType { NONE, PI_BUS_WRITE_COMPLETE, PI_DMA_COMPLETE, SI_DMA, IMPOSSIBLE }; +enum EventType { NONE, VI_HALFLINE, COMPARE_INTERRUPT, PI_BUS_WRITE_COMPLETE, PI_DMA_COMPLETE, SI_DMA, IMPOSSIBLE }; struct Event { u64 time; @@ -30,6 +30,13 @@ struct IterableEvents { struct Scheduler { Scheduler() { EnqueueAbsolute(std::numeric_limits::max(), IMPOSSIBLE); } + void Reset() { + ticks = 0; + index = 0; + events.events = {}; + EnqueueAbsolute(std::numeric_limits::max(), IMPOSSIBLE); + } + static Scheduler &GetInstance() { static Scheduler instance; return instance; @@ -37,8 +44,8 @@ struct Scheduler { void EnqueueRelative(u64, EventType); void EnqueueAbsolute(u64, EventType); - [[nodiscard]] u64 Remove(EventType) const; - void Tick(u64 t); + u64 Remove(EventType) const; + void Tick(u64 t, float, float); u8 index = 0; u64 ticks = 0; diff --git a/src/backend/core/mmio/VI.cpp b/src/backend/core/mmio/VI.cpp index f4d790f..f70ac90 100644 --- a/src/backend/core/mmio/VI.cpp +++ b/src/backend/core/mmio/VI.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace n64 { VI::VI() { Reset(); } @@ -19,7 +20,10 @@ void VI::Reset() { hsyncLeap = {}, burst = {}, vburst = {}; hstart = {}, vstart = {}; isPal = false; - swaps = {}; + halfline = 0; + field = 0; + lastHalflineTime = 1000; + Scheduler::GetInstance().EnqueueRelative(cyclesPerHalfline, VI_HALFLINE); } u32 VI::Read(const u32 paddr) const { @@ -70,13 +74,7 @@ void VI::Write(const u32 paddr, const u32 val) { numFields = status.serrate ? 2 : 1; break; case 0x04400004: - { - const u32 masked = val & 0xFFFFFF; - if (origin != masked) { - swaps++; - } - origin = masked; - } + origin = val & 0xFFFFFF; break; case 0x04400008: width = val & 0x7FF; @@ -94,6 +92,9 @@ void VI::Write(const u32 paddr, const u32 val) { vsync = val & 0x3FF; numHalflines = vsync >> 1; cyclesPerHalfline = GetCyclesPerFrame(isPal) / numHalflines; + missingCycles = GetCyclesPerFrame(isPal) % numHalflines; + Scheduler::GetInstance().Remove(VI_HALFLINE); + Scheduler::GetInstance().EnqueueRelative((u64)cyclesPerHalfline, VI_HALFLINE); break; case 0x0440001C: hsync = val & 0x3FF; diff --git a/src/backend/core/mmio/VI.hpp b/src/backend/core/mmio/VI.hpp index 4df6ac1..a91c3f0 100644 --- a/src/backend/core/mmio/VI.hpp +++ b/src/backend/core/mmio/VI.hpp @@ -82,9 +82,12 @@ struct VI { u32 origin{}, width{}, current{}; u32 vsync{}, hsync{}, intr{}; AxisStart hstart{}, vstart{}; - int swaps{}; + int halfline{}; int numHalflines{}; int numFields{}; int cyclesPerHalfline{}; + int field{}; + int missingCycles{}; + u64 lastHalflineTime; }; } // namespace n64 diff --git a/src/frontend/EmuThread.cpp b/src/frontend/EmuThread.cpp index 7d54327..983ada2 100644 --- a/src/frontend/EmuThread.cpp +++ b/src/frontend/EmuThread.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace std::chrono_literals; @@ -10,7 +11,8 @@ EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings( void EmuThread::run() noexcept { n64::Core& core = n64::Core::GetInstance(); - rdramCopy = core.GetMem().GetRDRAM(); + if(!core.romLoaded) return; + if(core.pause) return; while(!shouldExit) { if(!core.romLoaded) { @@ -23,12 +25,10 @@ void EmuThread::run() noexcept { continue; } - core.Run(settings.getVolumeL(), settings.getVolumeR()); - { - std::lock_guard lk(presentMutex); - memcpy(rdramCopy.data(), core.GetMem().GetRDRAMPtr(), RDRAM_SIZE); - } - readyForPresentation.notify_all(); + const auto volumeL = settings.getVolumeL(), volumeR = settings.getVolumeR(); + core.Run(volumeL, volumeR); + core.mem->mmio.ai.Step(core.cycles, volumeL, volumeR); + Scheduler::GetInstance().Tick(core.cycles, volumeL, volumeR); } } @@ -43,9 +43,7 @@ void EmuThread::TogglePause() const noexcept { } void EmuThread::Reset() noexcept { - n64::Core& core = n64::Core::GetInstance(); - core.Reset(); - rdramCopy = core.GetMem().GetRDRAM(); + n64::Core::GetInstance().Reset(); } void EmuThread::Stop() noexcept { diff --git a/src/frontend/EmuThread.hpp b/src/frontend/EmuThread.hpp index 23478f5..9f72e8f 100644 --- a/src/frontend/EmuThread.hpp +++ b/src/frontend/EmuThread.hpp @@ -23,7 +23,4 @@ public: SettingsWindow &settings; double& fps; std::atomic_bool shouldExit = false; - std::mutex presentMutex; - std::condition_variable readyForPresentation; - std::vector rdramCopy; }; diff --git a/src/frontend/KaizenGui.cpp b/src/frontend/KaizenGui.cpp index 9dd4e7e..06cac8f 100644 --- a/src/frontend/KaizenGui.cpp +++ b/src/frontend/KaizenGui.cpp @@ -10,7 +10,7 @@ using namespace std::chrono_literals; KaizenGui::KaizenGui() noexcept : window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), emuThread(fpsCounter, settingsWindow), - vulkanWidget(window.getHandle(), emuThread.rdramCopy.data()) { + vulkanWidget(window.getHandle(), n64::Core::GetMem().GetRDRAMPtr()) { gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); @@ -440,16 +440,21 @@ void KaizenGui::RenderUI() { if (minimized) return; - if (core.romLoaded) { - std::unique_lock lk(emuThread.presentMutex); - if (emuThread.readyForPresentation.wait_for(lk, 16.6667ms) == std::cv_status::no_timeout) - core.parallel.UpdateScreen(); - else - core.parallel.UpdateScreen(); + + if(!core.romLoaded) { + core.parallel.UpdateScreen(); return; } - core.parallel.UpdateScreen(); + { + std::unique_lock lk(core.mtxPresentationReady); + core.readyForPresentation.wait_for(lk, 16.66667ms); + } + core.parallel.UpdateScreen(); + { + std::lock_guard lk(core.mtxPresentationFinished); + core.finishedPresentation.notify_all(); + } } void KaizenGui::LoadROM(const std::string &path) noexcept { @@ -461,6 +466,7 @@ void KaizenGui::LoadROM(const std::string &path) noexcept { } void KaizenGui::run() { + n64::Core& core = n64::Core::GetInstance(); while (!quit) { SDL_Event e; while (SDL_PollEvent(&e)) { @@ -483,7 +489,7 @@ void KaizenGui::run() { HandleInput(e); } - SDL_GetWindowSize(window.getHandle(), &width, &height); + SDL_GetWindowSize(window.getHandle(), &width, &height); RenderUI(); } }