mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 07:05:40 +12:00
commit
c3487251b6
6 changed files with 379 additions and 364 deletions
|
@ -187,7 +187,7 @@ if(ENABLE_QT_GUI)
|
||||||
source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES})
|
source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES})
|
||||||
include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
else()
|
else()
|
||||||
set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp)
|
set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp)
|
||||||
set(FRONTEND_HEADER_FILES "")
|
set(FRONTEND_HEADER_FILES "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -25,6 +23,8 @@
|
||||||
#include "gl/context.h"
|
#include "gl/context.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class SDL_Window;
|
||||||
|
|
||||||
enum class ROMType {
|
enum class ROMType {
|
||||||
None,
|
None,
|
||||||
ELF,
|
ELF,
|
||||||
|
@ -42,17 +42,6 @@ class Emulator {
|
||||||
Crypto::AESEngine aesEngine;
|
Crypto::AESEngine aesEngine;
|
||||||
Cheats cheats;
|
Cheats cheats;
|
||||||
|
|
||||||
#ifdef PANDA3DS_FRONTEND_SDL
|
|
||||||
SDL_Window* window;
|
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
|
||||||
SDL_GLContext glContext;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SDL_GameController* gameController = nullptr;
|
|
||||||
int gameControllerID;
|
|
||||||
|
|
||||||
// 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
|
||||||
// And so the user can still use the keyboard to control the analog
|
// And so the user can still use the keyboard to control the analog
|
||||||
|
@ -100,7 +89,7 @@ class Emulator {
|
||||||
void step();
|
void step();
|
||||||
void render();
|
void render();
|
||||||
void reset(ReloadOption reload);
|
void reset(ReloadOption reload);
|
||||||
void run();
|
void run(void* frontend = nullptr);
|
||||||
void runFrame();
|
void runFrame();
|
||||||
|
|
||||||
void resume(); // Resume the emulator
|
void resume(); // Resume the emulator
|
||||||
|
@ -118,7 +107,7 @@ class Emulator {
|
||||||
// For passing the GL context from Qt to the renderer
|
// For passing the GL context from Qt to the renderer
|
||||||
void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); }
|
void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); }
|
||||||
#else
|
#else
|
||||||
void initGraphicsContext() { gpu.initGraphicsContext(window); }
|
void initGraphicsContext(SDL_Window* window) { gpu.initGraphicsContext(window); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
||||||
|
|
21
include/panda_sdl/frontend_sdl.hpp
Normal file
21
include/panda_sdl/frontend_sdl.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "emulator.hpp"
|
||||||
|
|
||||||
|
class FrontendSDL {
|
||||||
|
Emulator emu;
|
||||||
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
|
SDL_GLContext glContext;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrontendSDL();
|
||||||
|
bool loadROM(const std::filesystem::path& path);
|
||||||
|
void run();
|
||||||
|
|
||||||
|
SDL_Window* window = nullptr;
|
||||||
|
SDL_GameController* gameController = nullptr;
|
||||||
|
int gameControllerID;
|
||||||
|
};
|
345
src/emulator.cpp
345
src/emulator.cpp
|
@ -1,6 +1,7 @@
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
|
|
||||||
#include <glad/gl.h>
|
#include <SDL_filesystem.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -20,73 +21,12 @@ Emulator::Emulator()
|
||||||
, httpServer(this)
|
, httpServer(this)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
|
|
||||||
Helpers::panic("Failed to initialize SDL2");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make SDL use consistent positional button mapping
|
|
||||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
|
||||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
|
|
||||||
Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||||
if (config.discordRpcEnabled) {
|
if (config.discordRpcEnabled) {
|
||||||
discordRpc.init();
|
discordRpc.init();
|
||||||
updateDiscord();
|
updateDiscord();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// We need OpenGL for software rendering or for OpenGL if it's enabled
|
|
||||||
bool needOpenGL = config.rendererType == RendererType::Software;
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
|
||||||
needOpenGL = needOpenGL || (config.rendererType == RendererType::OpenGL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Only create SDL Window for SDL frontend
|
|
||||||
#ifdef PANDA3DS_FRONTEND_SDL
|
|
||||||
if (needOpenGL) {
|
|
||||||
// Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (max available on MacOS)
|
|
||||||
// MacOS gets mad if we don't explicitly demand a core profile
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.rendererType == RendererType::Software ? 3 : 4);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.rendererType == RendererType::Software ? 3 : 1);
|
|
||||||
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL);
|
|
||||||
|
|
||||||
if (window == nullptr) {
|
|
||||||
Helpers::panic("Window creation failed: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
glContext = SDL_GL_CreateContext(window);
|
|
||||||
if (glContext == nullptr) {
|
|
||||||
Helpers::panic("OpenGL context creation failed: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
|
||||||
Helpers::panic("OpenGL init failed: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_VULKAN
|
|
||||||
if (config.rendererType == RendererType::Vulkan) {
|
|
||||||
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN);
|
|
||||||
|
|
||||||
if (window == nullptr) {
|
|
||||||
Helpers::warn("Window creation failed: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
|
||||||
gameController = SDL_GameControllerOpen(0);
|
|
||||||
|
|
||||||
if (gameController != nullptr) {
|
|
||||||
SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController);
|
|
||||||
gameControllerID = SDL_JoystickInstanceID(stick);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(ReloadOption::NoReload);
|
reset(ReloadOption::NoReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,287 +71,6 @@ void Emulator::reset(ReloadOption reload) {
|
||||||
void Emulator::step() {}
|
void Emulator::step() {}
|
||||||
void Emulator::render() {}
|
void Emulator::render() {}
|
||||||
|
|
||||||
// Main loop for the SDL frontend. TODO: Move it to a dedicated file
|
|
||||||
#ifdef PANDA3DS_FRONTEND_SDL
|
|
||||||
void Emulator::run() {
|
|
||||||
programRunning = true;
|
|
||||||
|
|
||||||
while (programRunning) {
|
|
||||||
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
|
||||||
httpServer.processActions();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
runFrame();
|
|
||||||
HIDService& hid = kernel.getServiceManager().getHID();
|
|
||||||
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
namespace Keys = HID::Keys;
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
printf("Bye :(\n");
|
|
||||||
programRunning = false;
|
|
||||||
return;
|
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
|
|
||||||
switch (event.key.keysym.sym) {
|
|
||||||
case SDLK_l: hid.pressKey(Keys::A); break;
|
|
||||||
case SDLK_k: hid.pressKey(Keys::B); break;
|
|
||||||
case SDLK_o: hid.pressKey(Keys::X); break;
|
|
||||||
case SDLK_i: hid.pressKey(Keys::Y); break;
|
|
||||||
|
|
||||||
case SDLK_q: hid.pressKey(Keys::L); break;
|
|
||||||
case SDLK_p: hid.pressKey(Keys::R); break;
|
|
||||||
|
|
||||||
case SDLK_RIGHT: hid.pressKey(Keys::Right); break;
|
|
||||||
case SDLK_LEFT: hid.pressKey(Keys::Left); break;
|
|
||||||
case SDLK_UP: hid.pressKey(Keys::Up); break;
|
|
||||||
case SDLK_DOWN: hid.pressKey(Keys::Down); break;
|
|
||||||
|
|
||||||
case SDLK_w:
|
|
||||||
hid.setCirclepadY(0x9C);
|
|
||||||
keyboardAnalogY = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_a:
|
|
||||||
hid.setCirclepadX(-0x9C);
|
|
||||||
keyboardAnalogX = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_s:
|
|
||||||
hid.setCirclepadY(-0x9C);
|
|
||||||
keyboardAnalogY = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_d:
|
|
||||||
hid.setCirclepadX(0x9C);
|
|
||||||
keyboardAnalogX = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_RETURN: hid.pressKey(Keys::Start); break;
|
|
||||||
case SDLK_BACKSPACE: hid.pressKey(Keys::Select); break;
|
|
||||||
|
|
||||||
// Use the F4 button as a hot-key to pause or resume the emulator
|
|
||||||
// We can't use the audio play/pause buttons because it's annoying
|
|
||||||
case SDLK_F4: {
|
|
||||||
togglePause();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use F5 as a reset button
|
|
||||||
case SDLK_F5: {
|
|
||||||
reset(ReloadOption::Reload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_KEYUP:
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
|
|
||||||
switch (event.key.keysym.sym) {
|
|
||||||
case SDLK_l: hid.releaseKey(Keys::A); break;
|
|
||||||
case SDLK_k: hid.releaseKey(Keys::B); break;
|
|
||||||
case SDLK_o: hid.releaseKey(Keys::X); break;
|
|
||||||
case SDLK_i: hid.releaseKey(Keys::Y); break;
|
|
||||||
|
|
||||||
case SDLK_q: hid.releaseKey(Keys::L); break;
|
|
||||||
case SDLK_p: hid.releaseKey(Keys::R); break;
|
|
||||||
|
|
||||||
case SDLK_RIGHT: hid.releaseKey(Keys::Right); break;
|
|
||||||
case SDLK_LEFT: hid.releaseKey(Keys::Left); break;
|
|
||||||
case SDLK_UP: hid.releaseKey(Keys::Up); break;
|
|
||||||
case SDLK_DOWN: hid.releaseKey(Keys::Down); break;
|
|
||||||
|
|
||||||
// Err this is probably not ideal
|
|
||||||
case SDLK_w:
|
|
||||||
case SDLK_s:
|
|
||||||
hid.setCirclepadY(0);
|
|
||||||
keyboardAnalogY = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_a:
|
|
||||||
case SDLK_d:
|
|
||||||
hid.setCirclepadX(0);
|
|
||||||
keyboardAnalogX = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDLK_RETURN: hid.releaseKey(Keys::Start); break;
|
|
||||||
case SDLK_BACKSPACE: hid.releaseKey(Keys::Select); break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
|
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
||||||
const s32 x = event.button.x;
|
|
||||||
const s32 y = event.button.y;
|
|
||||||
|
|
||||||
// Check if touch falls in the touch screen area
|
|
||||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
|
||||||
// Convert to 3DS coordinates
|
|
||||||
u16 x_converted = static_cast<u16>(x) - 40;
|
|
||||||
u16 y_converted = static_cast<u16>(y) - 240;
|
|
||||||
|
|
||||||
hid.setTouchScreenPress(x_converted, y_converted);
|
|
||||||
} else {
|
|
||||||
hid.releaseTouchScreen();
|
|
||||||
}
|
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
|
||||||
holdingRightClick = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
|
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
||||||
hid.releaseTouchScreen();
|
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
|
||||||
holdingRightClick = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_CONTROLLERDEVICEADDED:
|
|
||||||
if (gameController == nullptr) {
|
|
||||||
gameController = SDL_GameControllerOpen(event.cdevice.which);
|
|
||||||
gameControllerID = event.cdevice.which;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_CONTROLLERDEVICEREMOVED:
|
|
||||||
if (event.cdevice.which == gameControllerID) {
|
|
||||||
SDL_GameControllerClose(gameController);
|
|
||||||
gameController = nullptr;
|
|
||||||
gameControllerID = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_CONTROLLERBUTTONUP:
|
|
||||||
case SDL_CONTROLLERBUTTONDOWN: {
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
u32 key = 0;
|
|
||||||
|
|
||||||
switch (event.cbutton.button) {
|
|
||||||
case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break;
|
|
||||||
case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key != 0) {
|
|
||||||
if (event.cbutton.state == SDL_PRESSED) {
|
|
||||||
hid.pressKey(key);
|
|
||||||
} else {
|
|
||||||
hid.releaseKey(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect mouse motion events for gyroscope emulation
|
|
||||||
case SDL_MOUSEMOTION: {
|
|
||||||
if (romType == ROMType::None) break;
|
|
||||||
|
|
||||||
// Handle "dragging" across the touchscreen
|
|
||||||
if (hid.isTouchScreenPressed()) {
|
|
||||||
const s32 x = event.motion.x;
|
|
||||||
const s32 y = event.motion.y;
|
|
||||||
|
|
||||||
// Check if touch falls in the touch screen area and register the new touch screen position
|
|
||||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
|
||||||
// Convert to 3DS coordinates
|
|
||||||
u16 x_converted = static_cast<u16>(x) - 40;
|
|
||||||
u16 y_converted = static_cast<u16>(y) - 240;
|
|
||||||
|
|
||||||
hid.setTouchScreenPress(x_converted, y_converted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation
|
|
||||||
if (holdingRightClick) {
|
|
||||||
// Relative motion since last mouse motion event
|
|
||||||
const s32 motionX = event.motion.xrel;
|
|
||||||
const s32 motionY = event.motion.yrel;
|
|
||||||
|
|
||||||
// The gyroscope involves lots of weird math I don't want to bother with atm
|
|
||||||
// So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion
|
|
||||||
const s32 roll = motionX > 0 ? 0x7f : -0x7f;
|
|
||||||
const s32 pitch = motionY > 0 ? 0x7f : -0x7f;
|
|
||||||
hid.setRoll(roll);
|
|
||||||
hid.setPitch(pitch);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SDL_DROPFILE: {
|
|
||||||
char* droppedDir = event.drop.file;
|
|
||||||
|
|
||||||
if (droppedDir) {
|
|
||||||
const std::filesystem::path path(droppedDir);
|
|
||||||
|
|
||||||
if (path.extension() == ".amiibo") {
|
|
||||||
loadAmiibo(path);
|
|
||||||
} else if (path.extension() == ".lua") {
|
|
||||||
lua.loadFile(droppedDir);
|
|
||||||
} else {
|
|
||||||
loadROM(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_free(droppedDir);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update controller analog sticks and HID service
|
|
||||||
if (romType != ROMType::None) {
|
|
||||||
if (gameController != nullptr) {
|
|
||||||
const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
|
||||||
const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
|
||||||
constexpr s16 deadzone = 3276;
|
|
||||||
constexpr s16 maxValue = 0x9C;
|
|
||||||
constexpr s16 div = 0x8000 / maxValue;
|
|
||||||
|
|
||||||
// Avoid overriding the keyboard's circlepad input
|
|
||||||
if (abs(stickX) < deadzone && !keyboardAnalogX) {
|
|
||||||
hid.setCirclepadX(0);
|
|
||||||
} else {
|
|
||||||
hid.setCirclepadX(stickX / div);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abs(stickY) < deadzone && !keyboardAnalogY) {
|
|
||||||
hid.setCirclepadY(0);
|
|
||||||
} else {
|
|
||||||
hid.setCirclepadY(-(stickY / div));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hid.updateInputs(cpu.getTicks());
|
|
||||||
}
|
|
||||||
// TODO: Should this be uncommented?
|
|
||||||
// kernel.evalReschedule();
|
|
||||||
|
|
||||||
// Update inputs in the HID module
|
|
||||||
SDL_GL_SwapWindow(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Only resume if a ROM is properly loaded
|
// Only resume if a ROM is properly loaded
|
||||||
void Emulator::resume() { running = (romType != ROMType::None); }
|
void Emulator::resume() { running = (romType != ROMType::None); }
|
||||||
void Emulator::pause() { running = false; }
|
void Emulator::pause() { running = false; }
|
||||||
|
|
347
src/panda_sdl/frontend_sdl.cpp
Normal file
347
src/panda_sdl/frontend_sdl.cpp
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
#include "panda_sdl/frontend_sdl.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <glad/gl.h>
|
||||||
|
|
||||||
|
FrontendSDL::FrontendSDL() {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
|
||||||
|
Helpers::panic("Failed to initialize SDL2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make SDL use consistent positional button mapping
|
||||||
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
||||||
|
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
|
Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
||||||
|
gameController = SDL_GameControllerOpen(0);
|
||||||
|
|
||||||
|
if (gameController != nullptr) {
|
||||||
|
SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController);
|
||||||
|
gameControllerID = SDL_JoystickInstanceID(stick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmulatorConfig& config = emu.getConfig();
|
||||||
|
// We need OpenGL for software rendering or for OpenGL if it's enabled
|
||||||
|
bool needOpenGL = config.rendererType == RendererType::Software;
|
||||||
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
|
needOpenGL = needOpenGL || (config.rendererType == RendererType::OpenGL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (needOpenGL) {
|
||||||
|
// Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (max available on MacOS)
|
||||||
|
// MacOS gets mad if we don't explicitly demand a core profile
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.rendererType == RendererType::Software ? 3 : 4);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.rendererType == RendererType::Software ? 3 : 1);
|
||||||
|
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_OPENGL);
|
||||||
|
|
||||||
|
if (window == nullptr) {
|
||||||
|
Helpers::panic("Window creation failed: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
glContext = SDL_GL_CreateContext(window);
|
||||||
|
if (glContext == nullptr) {
|
||||||
|
Helpers::panic("OpenGL context creation failed: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||||
|
Helpers::panic("OpenGL init failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||||
|
if (config.rendererType == RendererType::Vulkan) {
|
||||||
|
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_VULKAN);
|
||||||
|
|
||||||
|
if (window == nullptr) {
|
||||||
|
Helpers::warn("Window creation failed: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
emu.initGraphicsContext(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FrontendSDL::loadROM(const std::filesystem::path& path) { return emu.loadROM(path); }
|
||||||
|
void FrontendSDL::run() { emu.run(this); }
|
||||||
|
|
||||||
|
void Emulator::run(void* frontend) {
|
||||||
|
FrontendSDL* frontendSDL = reinterpret_cast<FrontendSDL*>(frontend);
|
||||||
|
programRunning = true;
|
||||||
|
|
||||||
|
while (programRunning) {
|
||||||
|
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
|
||||||
|
httpServer.processActions();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
runFrame();
|
||||||
|
HIDService& hid = kernel.getServiceManager().getHID();
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
namespace Keys = HID::Keys;
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
printf("Bye :(\n");
|
||||||
|
programRunning = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_l: hid.pressKey(Keys::A); break;
|
||||||
|
case SDLK_k: hid.pressKey(Keys::B); break;
|
||||||
|
case SDLK_o: hid.pressKey(Keys::X); break;
|
||||||
|
case SDLK_i: hid.pressKey(Keys::Y); break;
|
||||||
|
|
||||||
|
case SDLK_q: hid.pressKey(Keys::L); break;
|
||||||
|
case SDLK_p: hid.pressKey(Keys::R); break;
|
||||||
|
|
||||||
|
case SDLK_RIGHT: hid.pressKey(Keys::Right); break;
|
||||||
|
case SDLK_LEFT: hid.pressKey(Keys::Left); break;
|
||||||
|
case SDLK_UP: hid.pressKey(Keys::Up); break;
|
||||||
|
case SDLK_DOWN: hid.pressKey(Keys::Down); break;
|
||||||
|
|
||||||
|
case SDLK_w:
|
||||||
|
hid.setCirclepadY(0x9C);
|
||||||
|
keyboardAnalogY = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_a:
|
||||||
|
hid.setCirclepadX(-0x9C);
|
||||||
|
keyboardAnalogX = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_s:
|
||||||
|
hid.setCirclepadY(-0x9C);
|
||||||
|
keyboardAnalogY = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_d:
|
||||||
|
hid.setCirclepadX(0x9C);
|
||||||
|
keyboardAnalogX = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_RETURN: hid.pressKey(Keys::Start); break;
|
||||||
|
case SDLK_BACKSPACE: hid.pressKey(Keys::Select); break;
|
||||||
|
|
||||||
|
// Use the F4 button as a hot-key to pause or resume the emulator
|
||||||
|
// We can't use the audio play/pause buttons because it's annoying
|
||||||
|
case SDLK_F4: {
|
||||||
|
togglePause();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use F5 as a reset button
|
||||||
|
case SDLK_F5: {
|
||||||
|
reset(ReloadOption::Reload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_KEYUP:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_l: hid.releaseKey(Keys::A); break;
|
||||||
|
case SDLK_k: hid.releaseKey(Keys::B); break;
|
||||||
|
case SDLK_o: hid.releaseKey(Keys::X); break;
|
||||||
|
case SDLK_i: hid.releaseKey(Keys::Y); break;
|
||||||
|
|
||||||
|
case SDLK_q: hid.releaseKey(Keys::L); break;
|
||||||
|
case SDLK_p: hid.releaseKey(Keys::R); break;
|
||||||
|
|
||||||
|
case SDLK_RIGHT: hid.releaseKey(Keys::Right); break;
|
||||||
|
case SDLK_LEFT: hid.releaseKey(Keys::Left); break;
|
||||||
|
case SDLK_UP: hid.releaseKey(Keys::Up); break;
|
||||||
|
case SDLK_DOWN: hid.releaseKey(Keys::Down); break;
|
||||||
|
|
||||||
|
// Err this is probably not ideal
|
||||||
|
case SDLK_w:
|
||||||
|
case SDLK_s:
|
||||||
|
hid.setCirclepadY(0);
|
||||||
|
keyboardAnalogY = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_a:
|
||||||
|
case SDLK_d:
|
||||||
|
hid.setCirclepadX(0);
|
||||||
|
keyboardAnalogX = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDLK_RETURN: hid.releaseKey(Keys::Start); break;
|
||||||
|
case SDLK_BACKSPACE: hid.releaseKey(Keys::Select); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
const s32 x = event.button.x;
|
||||||
|
const s32 y = event.button.y;
|
||||||
|
|
||||||
|
// Check if touch falls in the touch screen area
|
||||||
|
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||||
|
// Convert to 3DS coordinates
|
||||||
|
u16 x_converted = static_cast<u16>(x) - 40;
|
||||||
|
u16 y_converted = static_cast<u16>(y) - 240;
|
||||||
|
|
||||||
|
hid.setTouchScreenPress(x_converted, y_converted);
|
||||||
|
} else {
|
||||||
|
hid.releaseTouchScreen();
|
||||||
|
}
|
||||||
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
|
holdingRightClick = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
hid.releaseTouchScreen();
|
||||||
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
|
holdingRightClick = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
|
if (frontendSDL->gameController == nullptr) {
|
||||||
|
frontendSDL->gameController = SDL_GameControllerOpen(event.cdevice.which);
|
||||||
|
frontendSDL->gameControllerID = event.cdevice.which;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
if (event.cdevice.which == frontendSDL->gameControllerID) {
|
||||||
|
SDL_GameControllerClose(frontendSDL->gameController);
|
||||||
|
frontendSDL->gameController = nullptr;
|
||||||
|
frontendSDL->gameControllerID = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
|
case SDL_CONTROLLERBUTTONDOWN: {
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
u32 key = 0;
|
||||||
|
|
||||||
|
switch (event.cbutton.button) {
|
||||||
|
case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break;
|
||||||
|
case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != 0) {
|
||||||
|
if (event.cbutton.state == SDL_PRESSED) {
|
||||||
|
hid.pressKey(key);
|
||||||
|
} else {
|
||||||
|
hid.releaseKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect mouse motion events for gyroscope emulation
|
||||||
|
case SDL_MOUSEMOTION: {
|
||||||
|
if (romType == ROMType::None) break;
|
||||||
|
|
||||||
|
// Handle "dragging" across the touchscreen
|
||||||
|
if (hid.isTouchScreenPressed()) {
|
||||||
|
const s32 x = event.motion.x;
|
||||||
|
const s32 y = event.motion.y;
|
||||||
|
|
||||||
|
// Check if touch falls in the touch screen area and register the new touch screen position
|
||||||
|
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||||
|
// Convert to 3DS coordinates
|
||||||
|
u16 x_converted = static_cast<u16>(x) - 40;
|
||||||
|
u16 y_converted = static_cast<u16>(y) - 240;
|
||||||
|
|
||||||
|
hid.setTouchScreenPress(x_converted, y_converted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation
|
||||||
|
if (holdingRightClick) {
|
||||||
|
// Relative motion since last mouse motion event
|
||||||
|
const s32 motionX = event.motion.xrel;
|
||||||
|
const s32 motionY = event.motion.yrel;
|
||||||
|
|
||||||
|
// The gyroscope involves lots of weird math I don't want to bother with atm
|
||||||
|
// So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion
|
||||||
|
const s32 roll = motionX > 0 ? 0x7f : -0x7f;
|
||||||
|
const s32 pitch = motionY > 0 ? 0x7f : -0x7f;
|
||||||
|
hid.setRoll(roll);
|
||||||
|
hid.setPitch(pitch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_DROPFILE: {
|
||||||
|
char* droppedDir = event.drop.file;
|
||||||
|
|
||||||
|
if (droppedDir) {
|
||||||
|
const std::filesystem::path path(droppedDir);
|
||||||
|
|
||||||
|
if (path.extension() == ".amiibo") {
|
||||||
|
loadAmiibo(path);
|
||||||
|
} else if (path.extension() == ".lua") {
|
||||||
|
lua.loadFile(droppedDir);
|
||||||
|
} else {
|
||||||
|
loadROM(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(droppedDir);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update controller analog sticks and HID service
|
||||||
|
if (romType != ROMType::None) {
|
||||||
|
if (frontendSDL->gameController != nullptr) {
|
||||||
|
const s16 stickX = SDL_GameControllerGetAxis(frontendSDL->gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
const s16 stickY = SDL_GameControllerGetAxis(frontendSDL->gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
constexpr s16 deadzone = 3276;
|
||||||
|
constexpr s16 maxValue = 0x9C;
|
||||||
|
constexpr s16 div = 0x8000 / maxValue;
|
||||||
|
|
||||||
|
// Avoid overriding the keyboard's circlepad input
|
||||||
|
if (abs(stickX) < deadzone && !keyboardAnalogX) {
|
||||||
|
hid.setCirclepadX(0);
|
||||||
|
} else {
|
||||||
|
hid.setCirclepadX(stickX / div);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs(stickY) < deadzone && !keyboardAnalogY) {
|
||||||
|
hid.setCirclepadY(0);
|
||||||
|
} else {
|
||||||
|
hid.setCirclepadY(-(stickY / div));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hid.updateInputs(cpu.getTicks());
|
||||||
|
}
|
||||||
|
// TODO: Should this be uncommented?
|
||||||
|
// kernel.evalReschedule();
|
||||||
|
|
||||||
|
SDL_GL_SwapWindow(frontendSDL->window);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
#include "emulator.hpp"
|
#include "panda_sdl/frontend_sdl.hpp"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
Emulator emu;
|
FrontendSDL app;
|
||||||
emu.initGraphicsContext();
|
|
||||||
|
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
auto romPath = std::filesystem::current_path() / argv[1];
|
auto romPath = std::filesystem::current_path() / argv[1];
|
||||||
if (!emu.loadROM(romPath)) {
|
if (!app.loadROM(romPath)) {
|
||||||
// For some reason just .c_str() doesn't show the proper path
|
// For some reason just .c_str() doesn't show the proper path
|
||||||
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
|
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
|
||||||
}
|
}
|
||||||
|
@ -14,5 +13,5 @@ int main(int argc, char *argv[]) {
|
||||||
printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n");
|
printf("No ROM inserted! Load a ROM by dragging and dropping it into the emulator window!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
emu.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue