From fa82dad38d412b7710ea3d9187cf267555b85171 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:29:14 +0200 Subject: [PATCH] First stuff running with scheduler --- include/cpu_dynarmic.hpp | 30 ++++++-------------- include/emulator.hpp | 7 +++++ include/scheduler.hpp | 3 ++ src/core/CPU/cpu_dynarmic.cpp | 30 ++++++++++++++++---- src/emulator.cpp | 52 +++++++++++++++++++++++++++++------ 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index a3fb3378..1ec50d9e 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -94,7 +94,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { } void AddTicks(u64 ticks) override { - totalTicks += ticks; + scheduler.currentTimestamp += ticks; if (ticks > ticksLeft) { ticksLeft = 0; @@ -111,7 +111,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { return getCyclesForInstruction(isThumb, instruction); } - MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} + MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} }; class CPU { @@ -122,12 +122,13 @@ class CPU { Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; MyEnvironment env; Memory& mem; - Scheduler scheduler; + Scheduler& scheduler; + Emulator& emu; public: static constexpr u64 ticksPerSec = 268111856; - CPU(Memory& mem, Kernel& kernel); + CPU(Memory& mem, Kernel& kernel, Emulator& emu); void reset(); void setReg(int index, u32 value) { @@ -166,29 +167,14 @@ class CPU { } u64 getTicks() { - return env.totalTicks; + return scheduler.currentTimestamp; } // Get reference to tick count. Memory needs access to this u64& getTicksRef() { - return env.totalTicks; + return scheduler.currentTimestamp; } void clearCache() { jit->ClearCache(); } - - void runFrame() { - env.ticksLeft = ticksPerSec / 60; - execute: - const auto exitReason = jit->Run(); - - if (static_cast(exitReason) != 0) [[unlikely]] { - // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing - // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump - if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { - goto execute; - } else { - Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); - } - } - } + void runFrame(); }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index f4537425..55ed8499 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -15,6 +15,7 @@ #include "io_file.hpp" #include "lua_manager.hpp" #include "memory.hpp" +#include "scheduler.hpp" #ifdef PANDA3DS_ENABLE_HTTP_SERVER #include "http_server.hpp" @@ -42,6 +43,7 @@ class Emulator { Kernel kernel; Crypto::AESEngine aesEngine; Cheats cheats; + Scheduler scheduler; // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state @@ -85,6 +87,8 @@ class Emulator { // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current // ROM and just resets the emu enum class ReloadOption { NoReload, Reload }; + // Used in CPU::runFrame + bool frameDone = false; Emulator(); ~Emulator(); @@ -94,6 +98,8 @@ class Emulator { void reset(ReloadOption reload); void run(void* frontend = nullptr); void runFrame(); + // Poll the scheduler for events + void pollScheduler(); void resume(); // Resume the emulator void pause(); // Pause the emulator @@ -121,6 +127,7 @@ class Emulator { Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } LuaManager& getLua() { return lua; } + Scheduler& getScheduler() { return scheduler; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 3d2f582c..fb097618 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -18,6 +18,7 @@ struct Scheduler { using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; EventMap events; + u64 currentTimestamp = 0; u64 nextTimestamp = 0; // Set nextTimestamp to the timestamp of the next event @@ -38,6 +39,8 @@ struct Scheduler { }; void reset() { + currentTimestamp = 0; + // Clear any pending events events.clear(); // Add a dummy event to always keep the scheduler non-empty diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index caf8a66e..f373dc06 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -2,8 +2,9 @@ #include "cpu_dynarmic.hpp" #include "arm_defs.hpp" +#include "emulator.hpp" -CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, scheduler) { +CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) { cp15 = std::make_shared(); Dynarmic::A32::UserConfig config; @@ -28,12 +29,31 @@ void CPU::reset() { jit->ClearCache(); jit->Regs().fill(0); jit->ExtRegs().fill(0); +} - // Reset scheduler and add a VBlank event - scheduler.reset(); - scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); +void CPU::runFrame() { + emu.frameDone = false; - printf("%lld\n", scheduler.nextTimestamp); + while (!emu.frameDone) { + // Run CPU until the next scheduler event + env.ticksLeft = scheduler.nextTimestamp; + + execute: + const auto exitReason = jit->Run(); + + // Handle any scheduler events that need handling. + emu.pollScheduler(); + + if (static_cast(exitReason) != 0) [[unlikely]] { + // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing + // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump + if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { + goto execute; + } else { + Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); + } + } + } } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e94170a2..46efd209 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -17,10 +17,11 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; #endif Emulator::Emulator() - : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config), - memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) + : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), + cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) #ifdef PANDA3DS_ENABLE_HTTP_SERVER - , httpServer(this) + , + httpServer(this) #endif { #ifdef PANDA3DS_ENABLE_DISCORD_RPC @@ -45,6 +46,10 @@ void Emulator::reset(ReloadOption reload) { cpu.reset(); gpu.reset(); memory.reset(); + // Reset scheduler and add a VBlank event + scheduler.reset(); + scheduler.addEvent(Scheduler::EventType::VBlank, CPU::ticksPerSec / 60); + // Kernel must be reset last because it depends on CPU/Memory state kernel.reset(); @@ -99,12 +104,6 @@ void Emulator::runFrame() { if (running) { cpu.runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics - lua.signalEvent(LuaEvent::Frame); - - // Send VBlank interrupts - ServiceManager& srv = kernel.getServiceManager(); - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); // Run cheats if any are loaded if (cheats.haveCheats()) [[unlikely]] { @@ -117,6 +116,41 @@ void Emulator::runFrame() { } } +void Emulator::pollScheduler() { + auto& events = scheduler.events; + + // Pop events until there's none pending anymore + while (scheduler.currentTimestamp > scheduler.nextTimestamp) { + // Read event timestamp and type, pop it from the scheduler and handle it + auto [time, eventType] = std::move(*events.begin()); + events.erase(events.begin()); + + scheduler.updateNextTimestamp(); + + switch (eventType) { + case Scheduler::EventType::VBlank: { + // Signal that we've reached the end of a frame + frameDone = true; + lua.signalEvent(LuaEvent::Frame); + + // Send VBlank interrupts + ServiceManager& srv = kernel.getServiceManager(); + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + + // Queue next VBlank event + scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60); + break; + } + + default: { + Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast(eventType)); + break; + } + } + } +} + bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM if (romType != ROMType::None) {