Refactor so parallel-rdp is in charge of polling inputs and not the EmuThread

This commit is contained in:
SimoneN64
2024-09-25 22:09:44 +02:00
parent 56b9d69861
commit 2744de8d3e
14 changed files with 174 additions and 151 deletions

View File

@@ -2,6 +2,7 @@
#include <ParallelRDPWrapper.hpp>
#include <memory>
#include <rdp_device.hpp>
#include <core/mmio/VI.hpp>
using namespace Vulkan;
using namespace RDP;

View File

@@ -1,8 +1,11 @@
#pragma once
#include <backend/Core.hpp>
#include <rdp_device.hpp>
#include <wsi.hpp>
#include <SDL3/SDL.h>
#include <common.hpp>
namespace n64 {
struct VI;
}
class ParallelRDP {
public:

View File

@@ -3,7 +3,7 @@
#include <Scheduler.hpp>
namespace n64 {
Core::Core(ParallelRDP &parallel) : cpu(std::make_unique<Interpreter>(parallel)) {}
Core::Core() : cpu(std::make_unique<Interpreter>(parallel)) {}
void Core::Stop() {
render = false;
@@ -106,15 +106,15 @@ void Core::Serialize() {
}
void Core::Deserialize() {
std::vector<u8> dVER(serialized[slot].begin(), serialized[slot].begin() + verSize);
std::vector dVER(serialized[slot].begin(), serialized[slot].begin() + verSize);
if (dVER[0] != (KAIZEN_VERSION >> 8) || dVER[1] != (KAIZEN_VERSION >> 4) || dVER[2] != (KAIZEN_VERSION & 0xFF)) {
Util::panic("PROBLEMI!");
}
cpu->GetMem().Deserialize(
std::vector<u8>(serialized[slot].begin() + verSize, serialized[slot].begin() + verSize + memSize));
cpu->Deserialize(std::vector<u8>(serialized[slot].begin() + verSize + memSize,
serialized[slot].begin() + verSize + memSize + cpuSize));
std::vector(serialized[slot].begin() + verSize, serialized[slot].begin() + verSize + memSize));
cpu->Deserialize(
std::vector(serialized[slot].begin() + verSize + memSize, serialized[slot].begin() + verSize + memSize + cpuSize));
serialized[slot].erase(serialized[slot].begin(), serialized[slot].end());
}
} // namespace n64

View File

@@ -1,14 +1,12 @@
#pragma once
#include <ParallelRDPWrapper.hpp>
#include <backend/core/Interpreter.hpp>
#include <backend/core/JIT.hpp>
#include <string>
struct Window;
struct Event;
namespace n64 {
struct Core {
explicit Core(ParallelRDP &);
explicit Core();
void Stop();
void LoadROM(const std::string &);
[[nodiscard]] bool LoadTAS(const fs::path &) const;
@@ -29,5 +27,6 @@ struct Core {
std::vector<u8> serialized[10]{};
size_t memSize{}, cpuSize{}, verSize{};
int slot = 0;
ParallelRDP parallel;
};
} // namespace n64

View File

@@ -2,7 +2,6 @@
#include <capstone/capstone.h>
#include <utils/log.hpp>
#include <utils/MemoryHelpers.hpp>
#include <portable_endian_bswap.h>
#include <array>
struct Disassembler {
@@ -19,7 +18,7 @@ struct Disassembler {
return ret;
}
DisassemblyResult Disassemble(u32 address, u32 instruction) {
DisassemblyResult Disassemble(const u32 address, const u32 instruction) {
return details ? DisassembleDetailed(address, instruction) : DisassembleSimple(address, instruction);
}
@@ -29,7 +28,7 @@ private:
DisassemblyResult DisassembleDetailed(u32 address, u32 instruction);
DisassemblyResult DisassembleSimple(u32 address, u32 instruction);
Disassembler(bool rsp) : rsp(rsp) {
explicit Disassembler(const bool rsp) : rsp(rsp) {
if (cs_open(CS_ARCH_MIPS, static_cast<cs_mode>((rsp ? CS_MODE_32 : CS_MODE_64) | CS_MODE_BIG_ENDIAN), &handle) !=
CS_ERR_OK) {
Util::panic("Could not initialize {} disassembler!", rsp ? "RSP" : "CPU");
@@ -43,5 +42,5 @@ private:
bool rsp = false;
bool details = true;
csh handle;
csh handle{};
};

View File

@@ -2,6 +2,7 @@
#include <core/RSP.hpp>
#include <log.hpp>
#include <parallel-rdp/ParallelRDPWrapper.hpp>
#include <core/Mem.hpp>
namespace n64 {
RDP::RDP(Mem &mem, ParallelRDP &parallel) : mem(mem), parallel(parallel) {

View File

@@ -1,102 +1,53 @@
#include <Core.hpp>
#include <EmuThread.hpp>
#include <SDL3/SDL.h>
EmuThread::EmuThread(RenderWidget &renderWidget, SettingsWindow &settings) noexcept :
renderWidget(renderWidget), core(parallel), settings(settings) {}
EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, RenderWidget &renderWidget,
SettingsWindow &settings) noexcept : renderWidget(renderWidget), core(core), settings(settings) {}
[[noreturn]] void EmuThread::run() noexcept {
parallel.Init(renderWidget.qtVkInstanceFactory, renderWidget.wsiPlatform, renderWidget.windowInfo,
core.cpu->GetMem().GetRDRAMPtr());
core->parallel.Init(renderWidget.qtVkInstanceFactory, renderWidget.wsiPlatform, renderWidget.windowInfo,
core->cpu->GetMem().GetRDRAMPtr());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
bool controllerConnected = false;
if (SDL_AddGamepadMappingsFromFile("resources/gamecontrollerdb.txt") < 0) {
Util::warn("[SDL] Could not load game controller DB");
}
auto pollEvents = [&] {
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_EVENT_GAMEPAD_ADDED:
{
const int index = e.gdevice.which;
controller = SDL_OpenGamepad(index);
Util::info("Found controller!");
auto serial = SDL_GetGamepadSerial(controller);
auto name = SDL_GetGamepadName(controller);
auto path = SDL_GetGamepadPath(controller);
Util::info("\tName: {}", name ? name : "Not available");
Util::info("\tSerial: {}", serial ? serial : "Not available");
Util::info("\tPath: {}", path ? path : "Not available");
controllerConnected = true;
}
break;
case SDL_EVENT_GAMEPAD_REMOVED:
{
controllerConnected = false;
SDL_CloseGamepad(controller);
}
break;
}
}
};
while (!isInterruptionRequested()) {
if (!core.pause) {
core.Run(settings.getVolumeL(), settings.getVolumeR());
if (!core->pause) {
core->Run(settings.getVolumeL(), settings.getVolumeR());
}
if (core.render) {
parallel.UpdateScreen(core.cpu->GetMem().mmio.vi);
}
pollEvents();
if (controllerConnected) {
n64::PIF &pif = core.cpu->GetMem().mmio.si.pif;
pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::Key::Z,
SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::Key::DRight,
SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::RT,
SDL_GetGamepadButton(controller, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
float xclamped = SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(controller, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, -yclamped);
pif.UpdateAxis(0, n64::Controller::Axis::X, xclamped);
if (core->render) {
core->parallel.UpdateScreen(core->cpu->GetMem().mmio.vi);
}
}
SetRender(false);
Stop();
}
void EmuThread::TogglePause() const noexcept {
core->TogglePause();
Util::RPC::GetInstance().Update(core->pause ? Util::RPC::Paused : Util::RPC::GetInstance().GetState(),
core->cpu->GetMem().rom.gameNameDB,
core->cpu->GetMem().mmio.si.pif.movie.GetFilename());
}
void EmuThread::SetRender(bool v) const noexcept { core->render = v; }
void EmuThread::Reset() const noexcept {
core->pause = true;
core->Stop();
core->LoadROM(core->rom);
core->pause = false;
}
void EmuThread::Stop() const noexcept {
Util::RPC::GetInstance().Update(Util::RPC::Idling);
core->rom = {};
core->pause = true;
core->Stop();
}

View File

@@ -1,46 +1,27 @@
#pragma once
#include <Core.hpp>
#include <Discord.hpp>
#include <QThread>
#include <RenderWidget.hpp>
#include <SDL3/SDL_gamepad.h>
#include <SettingsWindow.hpp>
#include <memory>
namespace n64 {
struct Core;
}
class EmuThread : public QThread {
Q_OBJECT
RenderWidget &renderWidget;
public:
explicit EmuThread(RenderWidget &, SettingsWindow &) noexcept;
explicit EmuThread(const std::shared_ptr<n64::Core> &, RenderWidget &, SettingsWindow &) noexcept;
[[noreturn]] void run() noexcept override;
void TogglePause() const noexcept;
void SetRender(bool v) const noexcept;
void Reset() const noexcept;
void Stop() const noexcept;
SDL_Gamepad *controller{};
ParallelRDP parallel;
n64::Core core;
std::shared_ptr<n64::Core> core;
SettingsWindow &settings;
void TogglePause() {
core.TogglePause();
Util::RPC::GetInstance().Update(core.pause ? Util::RPC::Paused : Util::RPC::GetInstance().GetState(),
core.cpu->GetMem().rom.gameNameDB,
core.cpu->GetMem().mmio.si.pif.movie.GetFilename());
}
void SetRender(bool v) { core.render = v; }
void Reset() {
core.pause = true;
core.Stop();
core.LoadROM(core.rom);
core.pause = false;
}
void Stop() {
Util::RPC::GetInstance().Update(Util::RPC::Idling);
core.rom = {};
core.pause = true;
core.Stop();
}
};

View File

@@ -1,16 +1,17 @@
#include <Core.hpp>
#include <KaizenQt.hpp>
#include <QApplication>
#include <QDropEvent>
#include <QMessageBox>
#include <QMimeData>
#include <filesystem>
namespace fs = std::filesystem;
KaizenQt::KaizenQt() noexcept : QWidget(nullptr) {
mainWindow = std::make_unique<MainWindow>();
core = std::make_shared<n64::Core>();
mainWindow = std::make_unique<MainWindow>(core);
settingsWindow = std::make_unique<SettingsWindow>();
emuThread = std::make_unique<EmuThread>(*mainWindow->vulkanWidget, *settingsWindow);
emuThread = std::make_unique<EmuThread>(core, *mainWindow->vulkanWidget, *settingsWindow);
debugger = std::make_unique<Debugger>();
ConnectMainWindowSignalsToSlots();
@@ -53,9 +54,9 @@ void KaizenQt::LoadROM(const QString &fileName) noexcept {
mainWindow->actionReset->setEnabled(true);
mainWindow->actionStop->setEnabled(true);
emuThread->start();
emuThread->core.LoadROM(fileName.toStdString());
auto gameNameDB = emuThread->core.cpu->GetMem().rom.gameNameDB;
mainWindow->setWindowTitle(emuThread->core.cpu->GetMem().rom.gameNameDB.c_str());
emuThread->core->LoadROM(fileName.toStdString());
auto gameNameDB = emuThread->core->cpu->GetMem().rom.gameNameDB;
mainWindow->setWindowTitle(emuThread->core->cpu->GetMem().rom.gameNameDB.c_str());
Util::RPC::GetInstance().Update(Util::RPC::Playing, gameNameDB);
}
@@ -69,15 +70,19 @@ void KaizenQt::Quit() noexcept {
}
void KaizenQt::LoadTAS(const QString &fileName) const noexcept {
emuThread->core.LoadTAS(fs::path(fileName.toStdString()));
auto gameNameDB = emuThread->core.cpu->GetMem().rom.gameNameDB;
if (emuThread->core->LoadTAS(fs::path(fileName.toStdString()))) {
auto gameNameDB = emuThread->core->cpu->GetMem().rom.gameNameDB;
auto movieName = fs::path(fileName.toStdString()).stem().string();
Util::RPC::GetInstance().Update(Util::RPC::MovieReplay, gameNameDB, movieName);
return;
}
Util::panic("Could not load TAS movie {}!", fileName.toStdString());
}
void KaizenQt::keyPressEvent(QKeyEvent *e) {
emuThread->core.pause = true;
n64::Mem &mem = emuThread->core.cpu->GetMem();
emuThread->core->pause = true;
n64::Mem &mem = emuThread->core->cpu->GetMem();
n64::PIF &pif = mem.mmio.si.pif;
auto k = static_cast<Qt::Key>(e->key());
@@ -95,13 +100,13 @@ void KaizenQt::keyPressEvent(QKeyEvent *e) {
if (k == settingsWindow->keyMap[17])
pif.UpdateAxis(0, n64::Controller::Axis::X, 86);
emuThread->core.pause = false;
emuThread->core->pause = false;
QWidget::keyPressEvent(e);
}
void KaizenQt::keyReleaseEvent(QKeyEvent *e) {
emuThread->core.pause = true;
n64::Mem &mem = emuThread->core.cpu->GetMem();
emuThread->core->pause = true;
n64::Mem &mem = emuThread->core->cpu->GetMem();
n64::PIF &pif = mem.mmio.si.pif;
auto k = static_cast<Qt::Key>(e->key());
@@ -119,6 +124,6 @@ void KaizenQt::keyReleaseEvent(QKeyEvent *e) {
if (k == settingsWindow->keyMap[17])
pif.UpdateAxis(0, n64::Controller::Axis::X, 0);
emuThread->core.pause = false;
emuThread->core->pause = false;
QWidget::keyReleaseEvent(e);
}

View File

@@ -3,6 +3,7 @@
#include <EmuThread.hpp>
#include <MainWindow.hpp>
#include <SettingsWindow.hpp>
#include <log.hpp>
enum class CompositorCategory { Windows, MacOS, XCB, Wayland };
@@ -39,4 +40,5 @@ private:
std::unique_ptr<SettingsWindow> settingsWindow;
std::unique_ptr<EmuThread> emuThread;
std::unique_ptr<Debugger> debugger;
std::shared_ptr<n64::Core> core;
};

View File

@@ -1,6 +1,6 @@
#include <MainWindow.hpp>
MainWindow::MainWindow() noexcept {
MainWindow::MainWindow(const std::shared_ptr<n64::Core> &core) noexcept {
if (objectName().isEmpty())
setObjectName("MainWindow");
resize(800, 646);
@@ -26,7 +26,7 @@ MainWindow::MainWindow() noexcept {
verticalLayout->setSpacing(0);
verticalLayout->setObjectName("verticalLayout");
verticalLayout->setContentsMargins(0, 0, 0, 0);
vulkanWidget = std::make_unique<RenderWidget>();
vulkanWidget = std::make_unique<RenderWidget>(core);
vulkanWidget->setObjectName("vulkanWidget");
verticalLayout->addWidget(vulkanWidget.get());

View File

@@ -16,7 +16,7 @@ class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow() noexcept;
MainWindow(const std::shared_ptr<n64::Core> &) noexcept;
std::unique_ptr<QAction> actionOpenDebuggerWindow{};
std::unique_ptr<QAction> actionAbout{};

View File

@@ -1,7 +1,9 @@
#include <Core.hpp>
#include <KaizenQt.hpp>
#include <RenderWidget.hpp>
#include <SDL3/SDL_events.h>
RenderWidget::RenderWidget() : QWidget(nullptr) {
RenderWidget::RenderWidget(const std::shared_ptr<n64::Core> &core) : QWidget(nullptr) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
if (GetOSCompositorCategory() == CompositorCategory::Wayland) {
@@ -22,6 +24,76 @@ RenderWidget::RenderWidget() : QWidget(nullptr) {
windowHandle()->setVulkanInstance(&qtVkInstanceFactory->handle);
windowHandle()->create();
wsiPlatform = std::make_shared<QtWSIPlatform>(windowHandle());
wsiPlatform = std::make_shared<QtWSIPlatform>(core, windowHandle());
windowInfo = std::make_shared<QtParallelRdpWindowInfo>(windowHandle());
}
void QtWSIPlatform::poll_input() {
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_EVENT_GAMEPAD_ADDED:
{
const auto index = e.gdevice.which;
gamepad = SDL_OpenGamepad(index);
Util::info("Found controller!");
const auto serial = SDL_GetGamepadSerial(gamepad);
const auto name = SDL_GetGamepadName(gamepad);
const auto path = SDL_GetGamepadPath(gamepad);
Util::info("\tName: {}", name ? name : "Not available");
Util::info("\tSerial: {}", serial ? serial : "Not available");
Util::info("\tPath: {}", path ? path : "Not available");
gamepadConnected = true;
}
break;
case SDL_EVENT_GAMEPAD_REMOVED:
{
gamepadConnected = false;
SDL_CloseGamepad(gamepad);
}
break;
default:
break;
}
}
if (gamepadConnected) {
n64::PIF &pif = core->cpu->GetMem().mmio.si.pif;
pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));
pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST));
pif.UpdateButton(0, n64::Controller::Key::Z,
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX);
pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START));
pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP));
pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN));
pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT));
pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT));
pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER));
pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127);
pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127);
pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127);
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
if (xclamped < 0) {
xclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MAX));
} else {
xclamped /= SDL_JOYSTICK_AXIS_MAX;
}
xclamped *= 86;
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
if (yclamped < 0) {
yclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MIN));
} else {
yclamped /= SDL_JOYSTICK_AXIS_MAX;
}
yclamped *= 86;
pif.UpdateAxis(0, n64::Controller::Axis::Y, -yclamped);
pif.UpdateAxis(0, n64::Controller::Axis::X, xclamped);
}
}

View File

@@ -4,6 +4,11 @@
#include <QVulkanWindow>
#include <QWidget>
#include <QWindow>
#include <SDL3/SDL_gamepad.h>
namespace n64 {
struct Core;
}
struct QtInstanceFactory : Vulkan::InstanceFactory {
VkInstance create_instance(const VkInstanceCreateInfo *info) override {
@@ -40,7 +45,7 @@ private:
class QtWSIPlatform final : public Vulkan::WSIPlatform {
public:
explicit QtWSIPlatform(QWindow* window) : window(window) {}
explicit QtWSIPlatform(const std::shared_ptr<n64::Core> &core, QWindow *window) : window(window), core(core) {}
std::vector<const char *> get_instance_extensions() override {
auto vec = std::vector<const char *>();
@@ -64,7 +69,7 @@ public:
bool alive(Vulkan::WSI &) override { return true; }
void poll_input() override {}
void poll_input() override;
void poll_input_async(Granite::InputTrackerHandler *handler) override {}
void event_frame_tick(double frame, double elapsed) override {}
@@ -74,15 +79,19 @@ public:
VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3};
std::shared_ptr<QWindow> window{};
private:
std::shared_ptr<n64::Core> core;
SDL_Gamepad *gamepad{};
bool gamepadConnected = false;
};
class RenderWidget : public QWidget {
public:
[[nodiscard]] VkInstance instance() const { return qtVkInstanceFactory->handle.vkInstance(); }
explicit RenderWidget();
explicit RenderWidget(const std::shared_ptr<n64::Core> &);
[[nodiscard]] QPaintEngine *paintEngine() const override { return nullptr; }
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
std::shared_ptr<Vulkan::WSIPlatform> wsiPlatform;
std::shared_ptr<QtInstanceFactory> qtVkInstanceFactory;