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
+55 -41
View File
@@ -3,6 +3,8 @@
#include <Scheduler.hpp>
#include <Options.hpp>
using namespace std::chrono_literals;
namespace n64 {
Core::Core() {
const auto selectedCpu = Options::GetInstance().GetValue<std::string>("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<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) {
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
+5
View File
@@ -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> mem = std::make_unique<Mem>();
std::unique_ptr<BaseCPU> cpu;
std::mutex mtxPresentationFinished, mtxPresentationReady;
std::condition_variable readyForPresentation, finishedPresentation;
Registers regs;
ParallelRDP parallel;
};
+7 -5
View File
@@ -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<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;
}
events.pop();
}
}
+10 -3
View File
@@ -3,7 +3,7 @@
#include <log.hpp>
#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 {
u64 time;
@@ -30,6 +30,13 @@ struct IterableEvents {
struct Scheduler {
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 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;
+9 -8
View File
@@ -1,5 +1,6 @@
#include <Core.hpp>
#include <log.hpp>
#include <Scheduler.hpp>
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;
+4 -1
View File
@@ -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
+8 -10
View File
@@ -3,6 +3,7 @@
#include <KaizenGui.hpp>
#include <chrono>
#include <thread>
#include <Scheduler.hpp>
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<std::mutex> 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 {
-3
View File
@@ -23,7 +23,4 @@ public:
SettingsWindow &settings;
double& fps;
std::atomic_bool shouldExit = false;
std::mutex presentMutex;
std::condition_variable readyForPresentation;
std::vector<u8> rdramCopy;
};
+15 -9
View File
@@ -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<std::mutex> lk(emuThread.presentMutex);
if (emuThread.readyForPresentation.wait_for(lk, 16.6667ms) == std::cv_status::no_timeout)
core.parallel.UpdateScreen<true>();
else
core.parallel.UpdateScreen<false>();
if(!core.romLoaded) {
core.parallel.UpdateScreen<false>();
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 {
@@ -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();
}
}