It's definitely possible, just that the interpreter doesn't run often enough...

This commit is contained in:
2026-04-14 11:41:59 +02:00
parent e48f37fca1
commit 2f01416d21
9 changed files with 113 additions and 80 deletions
+51 -37
View File
@@ -3,6 +3,8 @@
#include <Scheduler.hpp> #include <Scheduler.hpp>
#include <Options.hpp> #include <Options.hpp>
using namespace std::chrono_literals;
namespace n64 { namespace n64 {
Core::Core() { Core::Core() {
const auto selectedCpu = Options::GetInstance().GetValue<std::string>("cpu", "type"); const auto selectedCpu = Options::GetInstance().GetValue<std::string>("cpu", "type");
@@ -28,8 +30,10 @@ void Core::Stop() {
} }
void Core::Reset() { void Core::Reset() {
Scheduler::GetInstance().Reset();
regs.Reset(); regs.Reset();
mem->Reset(); mem->Reset();
cpu->Reset(); cpu->Reset();
if(romLoaded) if(romLoaded)
mem->mmio.si.pif.Execute(); 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<std::mutex> lk(mtxPresentationReady);
readyForPresentation.notify_all();
}
{
std::unique_lock<std::mutex> lk(mtxPresentationFinished);
finishedPresentation.wait_for(lk, 1ms);
}
//parallel.UpdateScreen<true>();
}
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) { void Core::Run(const float volumeL, const float volumeR) {
MMIO &mmio = mem->mmio; MMIO &mmio = mem->mmio;
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(); const u32 taken = StepCPU();
cycles += taken; cycles += taken;
if((broken = breakpoints.contains(regs.nextPC)))
break;
StepRSP(taken); 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;
} }
} // namespace n64 } // namespace n64
+5
View File
@@ -29,6 +29,8 @@ struct Core {
return *GetInstance().mem; return *GetInstance().mem;
} }
void VIHalflineComplete(u64 time, float, float);
u32 StepCPU(); u32 StepCPU();
void StepRSP(u32 cpuCycles); void StepRSP(u32 cpuCycles);
void Stop(); void Stop();
@@ -56,6 +58,9 @@ struct Core {
std::unique_ptr<Mem> mem = std::make_unique<Mem>(); std::unique_ptr<Mem> mem = std::make_unique<Mem>();
std::unique_ptr<BaseCPU> cpu; std::unique_ptr<BaseCPU> cpu;
std::mutex mtxPresentationFinished, mtxPresentationReady;
std::condition_variable readyForPresentation, finishedPresentation;
Registers regs; Registers regs;
ParallelRDP parallel; ParallelRDP parallel;
}; };
+7 -5
View File
@@ -18,15 +18,15 @@ u64 Scheduler::Remove(const EventType eventType) const {
return 0; return 0;
} }
void Scheduler::Tick(const u64 t) { void Scheduler::Tick(const u64 t, float volumeL, float volumeR) {
n64::Mem& mem = n64::Core::GetMem(); n64::Mem& mem = n64::Core::GetMem();
ticks += t; ticks += t;
n64::MI &mi = mem.mmio.mi; n64::MI &mi = mem.mmio.mi;
n64::SI &si = mem.mmio.si; n64::SI &si = mem.mmio.si;
n64::PI &pi = mem.mmio.pi; n64::PI &pi = mem.mmio.pi;
while (ticks >= events.top().time) { for (auto event = events.top(); ticks >= event.time; events.pop()) {
switch (const auto type = events.top().type) { switch (event.type) {
case SI_DMA: case SI_DMA:
si.DMA(); si.DMA();
break; break;
@@ -37,15 +37,17 @@ void Scheduler::Tick(const u64 t) {
case PI_BUS_WRITE_COMPLETE: case PI_BUS_WRITE_COMPLETE:
pi.ioBusy = false; pi.ioBusy = false;
break; break;
case VI_HALFLINE:
n64::Core::GetInstance().VIHalflineComplete(ticks, volumeL, volumeR);
break;
case NONE: case NONE:
break; break;
case IMPOSSIBLE: case IMPOSSIBLE:
Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness"); Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unrecognized rom endianness");
return; return;
default: default:
Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unknown scheduler event type {}", static_cast<int>(type)); Util::Error::GetInstance().Throw({Util::Error::Severity::UNRECOVERABLE}, {Util::Error::Type::ROM_LOAD_ERROR}, {}, {}, "Unknown scheduler event type {}", static_cast<int>(event.type));
return; return;
} }
events.pop();
} }
} }
+10 -3
View File
@@ -3,7 +3,7 @@
#include <log.hpp> #include <log.hpp>
#include <queue> #include <queue>
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 { struct Event {
u64 time; u64 time;
@@ -30,6 +30,13 @@ struct IterableEvents {
struct Scheduler { struct Scheduler {
Scheduler() { EnqueueAbsolute(std::numeric_limits<u64>::max(), IMPOSSIBLE); } Scheduler() { EnqueueAbsolute(std::numeric_limits<u64>::max(), IMPOSSIBLE); }
void Reset() {
ticks = 0;
index = 0;
events.events = {};
EnqueueAbsolute(std::numeric_limits<u64>::max(), IMPOSSIBLE);
}
static Scheduler &GetInstance() { static Scheduler &GetInstance() {
static Scheduler instance; static Scheduler instance;
return instance; return instance;
@@ -37,8 +44,8 @@ struct Scheduler {
void EnqueueRelative(u64, EventType); void EnqueueRelative(u64, EventType);
void EnqueueAbsolute(u64, EventType); void EnqueueAbsolute(u64, EventType);
[[nodiscard]] u64 Remove(EventType) const; u64 Remove(EventType) const;
void Tick(u64 t); void Tick(u64 t, float, float);
u8 index = 0; u8 index = 0;
u64 ticks = 0; u64 ticks = 0;
+9 -8
View File
@@ -1,5 +1,6 @@
#include <Core.hpp> #include <Core.hpp>
#include <log.hpp> #include <log.hpp>
#include <Scheduler.hpp>
namespace n64 { namespace n64 {
VI::VI() { Reset(); } VI::VI() { Reset(); }
@@ -19,7 +20,10 @@ void VI::Reset() {
hsyncLeap = {}, burst = {}, vburst = {}; hsyncLeap = {}, burst = {}, vburst = {};
hstart = {}, vstart = {}; hstart = {}, vstart = {};
isPal = false; isPal = false;
swaps = {}; halfline = 0;
field = 0;
lastHalflineTime = 1000;
Scheduler::GetInstance().EnqueueRelative(cyclesPerHalfline, VI_HALFLINE);
} }
u32 VI::Read(const u32 paddr) const { 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; numFields = status.serrate ? 2 : 1;
break; break;
case 0x04400004: case 0x04400004:
{ origin = val & 0xFFFFFF;
const u32 masked = val & 0xFFFFFF;
if (origin != masked) {
swaps++;
}
origin = masked;
}
break; break;
case 0x04400008: case 0x04400008:
width = val & 0x7FF; width = val & 0x7FF;
@@ -94,6 +92,9 @@ void VI::Write(const u32 paddr, const u32 val) {
vsync = val & 0x3FF; vsync = val & 0x3FF;
numHalflines = vsync >> 1; numHalflines = vsync >> 1;
cyclesPerHalfline = GetCyclesPerFrame(isPal) / numHalflines; cyclesPerHalfline = GetCyclesPerFrame(isPal) / numHalflines;
missingCycles = GetCyclesPerFrame(isPal) % numHalflines;
Scheduler::GetInstance().Remove(VI_HALFLINE);
Scheduler::GetInstance().EnqueueRelative((u64)cyclesPerHalfline, VI_HALFLINE);
break; break;
case 0x0440001C: case 0x0440001C:
hsync = val & 0x3FF; hsync = val & 0x3FF;
+4 -1
View File
@@ -82,9 +82,12 @@ struct VI {
u32 origin{}, width{}, current{}; u32 origin{}, width{}, current{};
u32 vsync{}, hsync{}, intr{}; u32 vsync{}, hsync{}, intr{};
AxisStart hstart{}, vstart{}; AxisStart hstart{}, vstart{};
int swaps{}; int halfline{};
int numHalflines{}; int numHalflines{};
int numFields{}; int numFields{};
int cyclesPerHalfline{}; int cyclesPerHalfline{};
int field{};
int missingCycles{};
u64 lastHalflineTime;
}; };
} // namespace n64 } // namespace n64
+8 -10
View File
@@ -3,6 +3,7 @@
#include <KaizenGui.hpp> #include <KaizenGui.hpp>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <Scheduler.hpp>
using namespace std::chrono_literals; using namespace std::chrono_literals;
@@ -10,7 +11,8 @@ EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(
void EmuThread::run() noexcept { void EmuThread::run() noexcept {
n64::Core& core = n64::Core::GetInstance(); n64::Core& core = n64::Core::GetInstance();
rdramCopy = core.GetMem().GetRDRAM(); if(!core.romLoaded) return;
if(core.pause) return;
while(!shouldExit) { while(!shouldExit) {
if(!core.romLoaded) { if(!core.romLoaded) {
@@ -23,12 +25,10 @@ void EmuThread::run() noexcept {
continue; continue;
} }
core.Run(settings.getVolumeL(), settings.getVolumeR()); const auto volumeL = settings.getVolumeL(), volumeR = settings.getVolumeR();
{ core.Run(volumeL, volumeR);
std::lock_guard<std::mutex> lk(presentMutex); core.mem->mmio.ai.Step(core.cycles, volumeL, volumeR);
memcpy(rdramCopy.data(), core.GetMem().GetRDRAMPtr(), RDRAM_SIZE); Scheduler::GetInstance().Tick(core.cycles, volumeL, volumeR);
}
readyForPresentation.notify_all();
} }
} }
@@ -43,9 +43,7 @@ void EmuThread::TogglePause() const noexcept {
} }
void EmuThread::Reset() noexcept { void EmuThread::Reset() noexcept {
n64::Core& core = n64::Core::GetInstance(); n64::Core::GetInstance().Reset();
core.Reset();
rdramCopy = core.GetMem().GetRDRAM();
} }
void EmuThread::Stop() noexcept { void EmuThread::Stop() noexcept {
-3
View File
@@ -23,7 +23,4 @@ public:
SettingsWindow &settings; SettingsWindow &settings;
double& fps; double& fps;
std::atomic_bool shouldExit = false; std::atomic_bool shouldExit = false;
std::mutex presentMutex;
std::condition_variable readyForPresentation;
std::vector<u8> rdramCopy;
}; };
+13 -7
View File
@@ -10,7 +10,7 @@ using namespace std::chrono_literals;
KaizenGui::KaizenGui() noexcept : KaizenGui::KaizenGui() noexcept :
window("Kaizen " KAIZEN_VERSION_STR, 1280, 720), settingsWindow(window), emuThread(fpsCounter, settingsWindow), 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()); gui::Initialize(n64::Core::GetInstance().parallel.wsi, window.getHandle());
SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_InitSubSystem(SDL_INIT_GAMEPAD);
@@ -440,16 +440,21 @@ void KaizenGui::RenderUI() {
if (minimized) if (minimized)
return; return;
if (core.romLoaded) {
std::unique_lock<std::mutex> lk(emuThread.presentMutex); if(!core.romLoaded) {
if (emuThread.readyForPresentation.wait_for(lk, 16.6667ms) == std::cv_status::no_timeout)
core.parallel.UpdateScreen<true>();
else
core.parallel.UpdateScreen<false>(); core.parallel.UpdateScreen<false>();
return; return;
} }
core.parallel.UpdateScreen<false>(); {
std::unique_lock<std::mutex> lk(core.mtxPresentationReady);
core.readyForPresentation.wait_for(lk, 16.66667ms);
}
core.parallel.UpdateScreen<true>();
{
std::lock_guard<std::mutex> lk(core.mtxPresentationFinished);
core.finishedPresentation.notify_all();
}
} }
void KaizenGui::LoadROM(const std::string &path) noexcept { void KaizenGui::LoadROM(const std::string &path) noexcept {
@@ -461,6 +466,7 @@ void KaizenGui::LoadROM(const std::string &path) noexcept {
} }
void KaizenGui::run() { void KaizenGui::run() {
n64::Core& core = n64::Core::GetInstance();
while (!quit) { while (!quit) {
SDL_Event e; SDL_Event e;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {