mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 22:55:40 +12:00
First stuff running with scheduler
This commit is contained in:
parent
97f97dc6e2
commit
fa82dad38d
5 changed files with 86 additions and 36 deletions
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
|
@ -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(); }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue