Merge pull request #13 from wheremyfoodat/HID

HID service make-over
This commit is contained in:
wheremyfoodat 2023-06-06 18:42:53 +03:00 committed by GitHub
commit c7e3343974
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 205 additions and 16 deletions

View file

@ -35,6 +35,8 @@ include_directories(${SDL2_INCLUDE_DIR})
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/third_party/boost")
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/third_party/boost")
set(Boost_NO_SYSTEM_PATHS ON)
add_compile_definitions(BOOST_NO_CXX98_FUNCTION_BASE) # Forbid Boost from using std::unary_function (Fixes MacOS build)
add_library(boost INTERFACE)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})

View file

@ -199,7 +199,8 @@ public:
return &objects[handle];
}
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.requestGPUInterrupt(type); }
ServiceManager& getServiceManager() { return serviceManager; }
void sendGPUInterrupt(GPUInterrupt type) { serviceManager.sendGPUInterrupt(type); }
void signalDSPEvents() { serviceManager.signalDSPEvents(); }
void updateInputs() { serviceManager.updateInputs(); }
};

View file

@ -6,6 +6,31 @@
#include "logger.hpp"
#include "memory.hpp"
namespace HID::Keys {
enum : u32 {
A = 1 << 0,
B = 1 << 1,
Select = 1 << 2,
Start = 1 << 3,
Right = 1 << 4,
Left = 1 << 5,
Up = 1 << 6,
Down = 1 << 7,
R = 1 << 8,
L = 1 << 9,
X = 1 << 10,
Y = 1 << 11,
GPIO0Inv = 1 << 12, // Inverted value of GPIO bit 0
GPIO14Inv = 1 << 13, // Inverted value of GPIO bit 14
CirclePadRight = 1 << 28, // X >= 41
CirclePadLeft = 1 << 29, // X <= -41
CirclePadUp = 1 << 30, // Y >= 41
CirclePadDown = 1u << 31 // Y <= -41
};
}
// Circular dependency because we need HID to spawn events
class Kernel;
@ -15,6 +40,16 @@ class HIDService {
Kernel& kernel;
u8* sharedMem = nullptr; // Pointer to HID shared memory
uint nextPadIndex;
uint nextTouchscreenIndex;
uint nextAccelerometerIndex;
uint nextGyroIndex;
u32 newButtons; // The button state currently being edited
u32 oldButtons; // The previous pad state
s16 circlePadX, circlePadY; // Circlepad state
bool accelerometerEnabled;
bool eventsInitialized;
bool gyroEnabled;
@ -30,11 +65,50 @@ class HIDService {
void getGyroscopeCoefficient(u32 messagePointer);
void getIPCHandles(u32 messagePointer);
// Don't call these prior to initializing shared mem pls
template <typename T>
T readSharedMem(size_t offset) {
return *(T*)&sharedMem[offset];
}
template <typename T>
void writeSharedMem(size_t offset, T value) {
*(T*)&sharedMem[offset] = value;
}
public:
HIDService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();
void handleSyncRequest(u32 messagePointer);
void updateInputs();
void pressKey(u32 mask) { newButtons |= mask; }
void releaseKey(u32 mask) { newButtons &= ~mask; }
void setCirclepadX(s16 x) {
circlePadX = x;
// Turn bits 28 and 29 off in the new button state, which indicate whether the circlepad is steering left or right
// Then, set them according to the new value of x
newButtons &= ~0x3000'0000;
if (x >= 41) // Pressing right
newButtons |= 1 << 28;
else if (x <= -41) // Pressing left
newButtons |= 1 << 29;
}
void setCirclepadY(s16 y) {
circlePadY = y;
// Turn bits 30 and 31 off in the new button state, which indicate whether the circlepad is steering up or down
// Then, set them according to the new value of y
newButtons &= ~0xC000'0000;
if (y >= 41) // Pressing up
newButtons |= 1 << 30;
else if (y <= -41) // Pressing down
newButtons |= 1 << 31;
}
void updateInputs(u64 currentTimestamp);
void setSharedMem(u8* ptr) {
sharedMem = ptr;

View file

@ -79,10 +79,15 @@ public:
void sendCommandToService(u32 messagePointer, Handle handle);
// Wrappers for communicating with certain services
void requestGPUInterrupt(GPUInterrupt type) { gsp_gpu.requestInterrupt(type); }
void sendGPUInterrupt(GPUInterrupt type) { gsp_gpu.requestInterrupt(type); }
void setGSPSharedMem(u8* ptr) { gsp_gpu.setSharedMem(ptr); }
void setHIDSharedMem(u8* ptr) { hid.setSharedMem(ptr); }
void signalDSPEvents() { dsp.signalEvents(); }
void updateInputs() { hid.updateInputs(); }
void pressKey(u32 key) { hid.pressKey(key); }
void releaseKey(u32 key) { hid.releaseKey(key); }
void setCirclepadX(u16 x) { hid.setCirclepadX(x); }
void setCirclepadY(u16 y) { hid.setCirclepadY(y); }
void updateInputs(u64 currentTimestamp) { hid.updateInputs(currentTimestamp); }
};

View file

@ -5,9 +5,7 @@ Panda3DS is an HLE, red-panda-themed Nintendo 3DS emulator written in C++ which
![screenshot1](docs/img/MK7_Car_Select.png) ![screenshot2](docs/img/OoT_Title.png) ![screenshot3](docs/img/Mayro.png)
# Compatibility
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, performance leaves a bit to be desired mainly thanks to lack of shader acceleration, and most QoL features (including input, or a GUI!) are missing.
In fact, the screenshots in the repo were created after I hooked the input state to rand() locally.
Panda3DS is still in the early stages of development. Many games boot, many don't. Most games have at least some hilariously broken graphics, audio is not supported, performance leaves a bit to be desired mainly thanks to lack of shader acceleration, and most QoL features (including a GUI) are missing.
In addition, some games don't quiiite work with the upstream code. A lot of them might need some panics in the source code to be commented out before they work, etc. However, just the fact things can work as well as they do now is promising in itself.

View file

@ -30,6 +30,12 @@ void HIDService::reset() {
for (auto& e : events) {
e = std::nullopt;
}
// Reset indices for the various HID shared memory entries
nextPadIndex = nextTouchscreenIndex = nextAccelerometerIndex = nextGyroIndex = 0;
// Reset button states
newButtons = oldButtons = 0;
circlePadX = circlePadY = 0;
}
void HIDService::handleSyncRequest(u32 messagePointer) {
@ -108,8 +114,58 @@ void HIDService::getIPCHandles(u32 messagePointer) {
}
}
// TODO: We don't currently have inputs but we must at least try to signal the HID key input events now and then
void HIDService::updateInputs() {
void HIDService::updateInputs(u64 currentTick) {
// Update shared memory if it has been initialized
if (sharedMem) {
// First, update the pad state
if (nextPadIndex == 0) {
writeSharedMem<u64>(0x8, readSharedMem<u64>(0x0)); // Copy previous tick count
writeSharedMem<u64>(0x0, currentTick); // Write new tick count
}
writeSharedMem<u32>(0x10, nextPadIndex); // Index last updated by the HID module
writeSharedMem<u32>(0x1C, newButtons); // Current PAD state
writeSharedMem<s16>(0x20, circlePadX); // Current circle pad state
writeSharedMem<s16>(0x22, circlePadY);
const size_t padEntryOffset = 0x28 + (nextPadIndex * 0x10); // Offset in the array of 8 pad entries
nextPadIndex = (nextPadIndex + 1) % 8; // Move to next entry
const u32 pressed = (newButtons ^ oldButtons) & newButtons; // Pressed buttons
const u32 released = (newButtons ^ oldButtons) & oldButtons; // Released buttons
oldButtons = newButtons;
writeSharedMem<u32>(padEntryOffset, newButtons);
writeSharedMem<u32>(padEntryOffset + 4, pressed);
writeSharedMem<u32>(padEntryOffset + 8, released);
writeSharedMem<s16>(padEntryOffset + 12, circlePadX);
writeSharedMem<s16>(padEntryOffset + 14, circlePadY);
// Next, update touchscreen state
if (nextTouchscreenIndex == 0) {
writeSharedMem<u64>(0xB0, readSharedMem<u64>(0xA8)); // Copy previous tick count
writeSharedMem<u64>(0xA8, currentTick); // Write new tick count
}
writeSharedMem<u32>(0xB8, nextTouchscreenIndex); // Index last updated by the HID module
nextTouchscreenIndex = (nextTouchscreenIndex + 1) % 8; // Move to next entry
// Next, update accelerometer state
if (nextAccelerometerIndex == 0) {
writeSharedMem<u64>(0x110, readSharedMem<u64>(0x108)); // Copy previous tick count
writeSharedMem<u64>(0x108, currentTick); // Write new tick count
}
writeSharedMem<u32>(0x118, nextAccelerometerIndex); // Index last updated by the HID module
nextAccelerometerIndex = (nextAccelerometerIndex + 1) % 8; // Move to next entry
// Next, update gyro state
if (nextGyroIndex == 0) {
writeSharedMem<u64>(0x160, readSharedMem<u64>(0x158)); // Copy previous tick count
writeSharedMem<u64>(0x158, currentTick); // Write new tick count
}
writeSharedMem<u32>(0x168, nextGyroIndex); // Index last updated by the HID module
nextGyroIndex = (nextGyroIndex + 1) % 32; // Move to next entry
}
// For some reason, the original developers decided to signal the HID events each time the OS rescanned inputs
// Rather than once every time the state of a key, or the accelerometer state, etc is updated
// This means that the OS will signal the events even if literally nothing happened

View file

@ -27,22 +27,75 @@ void Emulator::run() {
runFrame(); // Run 1 frame of instructions
gpu.display(); // Display graphics
// Send VBlank interrupts
kernel.sendGPUInterrupt(GPUInterrupt::VBlank0);
kernel.sendGPUInterrupt(GPUInterrupt::VBlank1);
ServiceManager& srv = kernel.getServiceManager();
// Update inputs in the HID module
kernel.updateInputs();
// Send VBlank interrupts
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
namespace Keys = HID::Keys;
switch (event.type) {
case SDL_QUIT:
printf("Bye :(\n");
running = false;
return;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_l: srv.pressKey(Keys::A); break;
case SDLK_k: srv.pressKey(Keys::B); break;
case SDLK_o: srv.pressKey(Keys::X); break;
case SDLK_i: srv.pressKey(Keys::Y); break;
case SDLK_q: srv.pressKey(Keys::L); break;
case SDLK_p: srv.pressKey(Keys::R); break;
case SDLK_RIGHT: srv.pressKey(Keys::Right); break;
case SDLK_LEFT: srv.pressKey(Keys::Left); break;
case SDLK_UP: srv.pressKey(Keys::Up); break;
case SDLK_DOWN: srv.pressKey(Keys::Down); break;
case SDLK_w: srv.setCirclepadY(0x9C); break;
case SDLK_a: srv.setCirclepadX(-0x9C); break;
case SDLK_s: srv.setCirclepadY(-0x9C); break;
case SDLK_d: srv.setCirclepadX(0x9C); break;
case SDLK_RETURN: srv.pressKey(Keys::Start); break;
case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break;
}
break;
case SDL_KEYUP:
switch (event.key.keysym.sym) {
case SDLK_l: srv.releaseKey(Keys::A); break;
case SDLK_k: srv.releaseKey(Keys::B); break;
case SDLK_o: srv.releaseKey(Keys::X); break;
case SDLK_i: srv.releaseKey(Keys::Y); break;
case SDLK_q: srv.releaseKey(Keys::L); break;
case SDLK_p: srv.releaseKey(Keys::R); break;
case SDLK_RIGHT: srv.releaseKey(Keys::Right); break;
case SDLK_LEFT: srv.releaseKey(Keys::Left); break;
case SDLK_UP: srv.releaseKey(Keys::Up); break;
case SDLK_DOWN: srv.releaseKey(Keys::Down); break;
// Err this is probably not ideal
case SDLK_w: srv.setCirclepadY(0); break;
case SDLK_a: srv.setCirclepadX(0); break;
case SDLK_s: srv.setCirclepadY(0); break;
case SDLK_d: srv.setCirclepadX(0); break;
case SDLK_RETURN: srv.releaseKey(Keys::Start); break;
case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break;
}
break;
}
}
// Update inputs in the HID module
srv.updateInputs(cpu.getTicks());
SDL_GL_SwapWindow(window);
}
}