First stuff running with scheduler

This commit is contained in:
wheremyfoodat 2024-01-22 02:29:14 +02:00
parent 97f97dc6e2
commit fa82dad38d
5 changed files with 86 additions and 36 deletions

View file

@ -94,7 +94,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
} }
void AddTicks(u64 ticks) override { void AddTicks(u64 ticks) override {
totalTicks += ticks; scheduler.currentTimestamp += ticks;
if (ticks > ticksLeft) { if (ticks > ticksLeft) {
ticksLeft = 0; ticksLeft = 0;
@ -111,7 +111,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks {
return getCyclesForInstruction(isThumb, instruction); 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 { class CPU {
@ -122,12 +122,13 @@ class CPU {
Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; Dynarmic::ExclusiveMonitor exclusiveMonitor{1};
MyEnvironment env; MyEnvironment env;
Memory& mem; Memory& mem;
Scheduler scheduler; Scheduler& scheduler;
Emulator& emu;
public: public:
static constexpr u64 ticksPerSec = 268111856; static constexpr u64 ticksPerSec = 268111856;
CPU(Memory& mem, Kernel& kernel); CPU(Memory& mem, Kernel& kernel, Emulator& emu);
void reset(); void reset();
void setReg(int index, u32 value) { void setReg(int index, u32 value) {
@ -166,29 +167,14 @@ class CPU {
} }
u64 getTicks() { u64 getTicks() {
return env.totalTicks; return scheduler.currentTimestamp;
} }
// Get reference to tick count. Memory needs access to this // Get reference to tick count. Memory needs access to this
u64& getTicksRef() { u64& getTicksRef() {
return env.totalTicks; return scheduler.currentTimestamp;
} }
void clearCache() { jit->ClearCache(); } void clearCache() { jit->ClearCache(); }
void runFrame();
void runFrame() {
env.ticksLeft = ticksPerSec / 60;
execute:
const auto exitReason = jit->Run();
if (static_cast<u32>(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<u32>(exitReason), getReg(15));
}
}
}
}; };

View file

@ -15,6 +15,7 @@
#include "io_file.hpp" #include "io_file.hpp"
#include "lua_manager.hpp" #include "lua_manager.hpp"
#include "memory.hpp" #include "memory.hpp"
#include "scheduler.hpp"
#ifdef PANDA3DS_ENABLE_HTTP_SERVER #ifdef PANDA3DS_ENABLE_HTTP_SERVER
#include "http_server.hpp" #include "http_server.hpp"
@ -42,6 +43,7 @@ class Emulator {
Kernel kernel; Kernel kernel;
Crypto::AESEngine aesEngine; Crypto::AESEngine aesEngine;
Cheats cheats; Cheats cheats;
Scheduler scheduler;
// Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // 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 // 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 // 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 // ROM and just resets the emu
enum class ReloadOption { NoReload, Reload }; enum class ReloadOption { NoReload, Reload };
// Used in CPU::runFrame
bool frameDone = false;
Emulator(); Emulator();
~Emulator(); ~Emulator();
@ -94,6 +98,8 @@ class Emulator {
void reset(ReloadOption reload); void reset(ReloadOption reload);
void run(void* frontend = nullptr); void run(void* frontend = nullptr);
void runFrame(); void runFrame();
// Poll the scheduler for events
void pollScheduler();
void resume(); // Resume the emulator void resume(); // Resume the emulator
void pause(); // Pause the emulator void pause(); // Pause the emulator
@ -121,6 +127,7 @@ class Emulator {
Cheats& getCheats() { return cheats; } Cheats& getCheats() { return cheats; }
ServiceManager& getServiceManager() { return kernel.getServiceManager(); } ServiceManager& getServiceManager() { return kernel.getServiceManager(); }
LuaManager& getLua() { return lua; } LuaManager& getLua() { return lua; }
Scheduler& getScheduler() { return scheduler; }
RendererType getRendererType() const { return config.rendererType; } RendererType getRendererType() const { return config.rendererType; }
Renderer* getRenderer() { return gpu.getRenderer(); } Renderer* getRenderer() { return gpu.getRenderer(); }

View file

@ -18,6 +18,7 @@ struct Scheduler {
using EventMap = boost::container::flat_multimap<Key, Val, std::less<Key>, boost::container::static_vector<std::pair<Key, Val>, size>>; using EventMap = boost::container::flat_multimap<Key, Val, std::less<Key>, boost::container::static_vector<std::pair<Key, Val>, size>>;
EventMap<u64, EventType, totalNumberOfEvents> events; EventMap<u64, EventType, totalNumberOfEvents> events;
u64 currentTimestamp = 0;
u64 nextTimestamp = 0; u64 nextTimestamp = 0;
// Set nextTimestamp to the timestamp of the next event // Set nextTimestamp to the timestamp of the next event
@ -38,6 +39,8 @@ struct Scheduler {
}; };
void reset() { void reset() {
currentTimestamp = 0;
// Clear any pending events // Clear any pending events
events.clear(); events.clear();
// Add a dummy event to always keep the scheduler non-empty // Add a dummy event to always keep the scheduler non-empty

View file

@ -2,8 +2,9 @@
#include "cpu_dynarmic.hpp" #include "cpu_dynarmic.hpp"
#include "arm_defs.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<CP15>(); cp15 = std::make_shared<CP15>();
Dynarmic::A32::UserConfig config; Dynarmic::A32::UserConfig config;
@ -28,12 +29,31 @@ void CPU::reset() {
jit->ClearCache(); jit->ClearCache();
jit->Regs().fill(0); jit->Regs().fill(0);
jit->ExtRegs().fill(0); jit->ExtRegs().fill(0);
}
// Reset scheduler and add a VBlank event void CPU::runFrame() {
scheduler.reset(); emu.frameDone = false;
scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60);
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<u32>(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<u32>(exitReason), getReg(15));
}
}
}
} }
#endif // CPU_DYNARMIC #endif // CPU_DYNARMIC

View file

@ -17,10 +17,11 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
#endif #endif
Emulator::Emulator() Emulator::Emulator()
: config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config), : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config),
memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false)
#ifdef PANDA3DS_ENABLE_HTTP_SERVER #ifdef PANDA3DS_ENABLE_HTTP_SERVER
, httpServer(this) ,
httpServer(this)
#endif #endif
{ {
#ifdef PANDA3DS_ENABLE_DISCORD_RPC #ifdef PANDA3DS_ENABLE_DISCORD_RPC
@ -45,6 +46,10 @@ void Emulator::reset(ReloadOption reload) {
cpu.reset(); cpu.reset();
gpu.reset(); gpu.reset();
memory.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 must be reset last because it depends on CPU/Memory state
kernel.reset(); kernel.reset();
@ -99,12 +104,6 @@ void Emulator::runFrame() {
if (running) { if (running) {
cpu.runFrame(); // Run 1 frame of instructions cpu.runFrame(); // Run 1 frame of instructions
gpu.display(); // Display graphics 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 // Run cheats if any are loaded
if (cheats.haveCheats()) [[unlikely]] { 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<int>(eventType));
break;
}
}
}
}
bool Emulator::loadROM(const std::filesystem::path& path) { bool Emulator::loadROM(const std::filesystem::path& path) {
// Reset the emulator if we've already loaded a ROM // Reset the emulator if we've already loaded a ROM
if (romType != ROMType::None) { if (romType != ROMType::None) {