It's definitely possible, just that the interpreter doesn't run often enough...
This commit is contained in:
+51
-37
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user