Move controller implementations from librecomp to ultramodern (#38)

* Start moving some cont functions to ultramodern

* Implement osContStartQuery, osContSetCh

* Finish moving cont.cpp functions to ultramodern

* Add constexpr to _return

* Fix rumble not working

* Rename cont.cpp to input.cpp

* Test
This commit is contained in:
Anghelo Carvajal 2024-06-06 13:45:33 -04:00 committed by GitHub
parent 6ceb171571
commit 2caf04e6da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 287 additions and 123 deletions

View file

@ -41,7 +41,7 @@ namespace recomp {
void set_rom_contents(std::vector<uint8_t>&& new_rom);
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
void start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks, const ultramodern::events::callbacks_t& thread_callbacks, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_);
void start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input::callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks, const ultramodern::events::callbacks_t& thread_callbacks, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_);
void start_game(const std::u8string& game_id);
std::u8string current_game_id();
}

View file

@ -39,12 +39,18 @@ T _arg(uint8_t* rdram, recomp_context* ctx) {
template <typename T>
void _return(recomp_context* ctx, T val) {
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");
if (std::is_same_v<T, float>) {
if constexpr (std::is_same_v<T, float>) {
ctx->f0.fl = val;
}
else if (std::is_integral_v<T> && sizeof(T) <= 4) {
else if constexpr (std::is_integral_v<T> && sizeof(T) <= 4) {
ctx->r2 = int32_t(val);
}
else {
// static_assert in else workaround
[] <bool flag = false>() {
static_assert(flag, "Unsupported type");
}();
}
}
#endif
#endif

View file

@ -1,14 +1,7 @@
#include <ultramodern/ultramodern.hpp>
#include "ultramodern/ultramodern.hpp"
#include "helpers.hpp"
static ultramodern::input_callbacks_t input_callbacks;
std::chrono::high_resolution_clock::time_point input_poll_time;
void update_poll_time() {
input_poll_time = std::chrono::high_resolution_clock::now();
}
extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) {
// TODO reimplement the system for tagging polls with IDs to handle games with multithreaded input polling.
}
@ -17,139 +10,92 @@ extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) {
ultramodern::measure_input_latency();
}
void ultramodern::measure_input_latency() {
// printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
}
void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) {
input_callbacks = callbacks;
}
static int max_controllers = 0;
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) bitpattern = _arg<1, PTR(void)>(rdram, ctx);
PTR(void) status = _arg<2, PTR(void)>(rdram, ctx);
PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx);
PTR(u8) bitpattern = _arg<1, PTR(u8)>(rdram, ctx);
PTR(OSContStatus) data = _arg<2, PTR(OSContStatus)>(rdram, ctx);
// Set bit 0 to indicate that controller 0 is present
MEM_B(0, bitpattern) = 0x01;
s32 ret = osContInit(PASS_RDRAM mq, bitpattern, data);
// Mark controller 0 as present
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
_return<s32>(ctx, ret);
}
max_controllers = 4;
extern "C" void osContReset_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx);
PTR(OSContStatus) data = _arg<1, PTR(OSContStatus)>(rdram, ctx);
// Mark controllers 1-3 as not connected
for (size_t controller = 1; controller < max_controllers; controller++) {
// Libultra doesn't write status or type for absent controllers
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
s32 ret = osContReset(PASS_RDRAM mq, data);
_return<s32>(ctx, 0);
_return<s32>(ctx, ret);
}
extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
if (input_callbacks.poll_input) {
input_callbacks.poll_input();
}
update_poll_time();
PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx);
ultramodern::send_si_message(rdram);
s32 ret = osContStartReadData(PASS_RDRAM mq);
_return<s32>(ctx, ret);
}
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx);
PTR(OSContPad) data = _arg<0, PTR(OSContPad)>(rdram, ctx);
uint16_t buttons = 0;
float x = 0.0f;
float y = 0.0f;
if (input_callbacks.get_input) {
input_callbacks.get_input(&buttons, &x, &y);
}
if (max_controllers > 0) {
// button
MEM_H(0, pad) = buttons;
// stick_x
MEM_B(2, pad) = (int8_t)(127 * x);
// stick_y
MEM_B(3, pad) = (int8_t)(127 * y);
// errno
MEM_B(4, pad) = 0;
}
for (int controller = 1; controller < max_controllers; controller++) {
MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
osContGetReadData(PASS_RDRAM data);
}
extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
ultramodern::send_si_message(rdram);
PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx);
s32 ret = osContStartQuery(PASS_RDRAM mq);
_return<s32>(ctx, ret);
}
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
PTR(void) status = _arg<0, PTR(void)>(rdram, ctx);
PTR(OSContStatus) data = _arg<0, PTR(OSContStatus)>(rdram, ctx);
// Mark controller 0 as present
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
// Mark controllers 1-3 as not connected
for (size_t controller = 1; controller < max_controllers; controller++) {
// Libultra doesn't write status or type for absent controllers
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
osContGetQuery(PASS_RDRAM data);
}
extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
max_controllers = (std::min)(_arg<0, u8>(rdram, ctx), u8(4));
_return<s32>(ctx, 0);
u8 ch = _arg<0, u8>(rdram, ctx);
s32 ret = osContSetCh(PASS_RDRAM ch);
_return<s32>(ctx, ret);
}
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx);
s32 flag = _arg<1, s32>(rdram, ctx);
s32 channel = MEM_W(8, pfs);
// Only respect accesses to controller 0.
if (channel == 0) {
input_callbacks.set_rumble(flag);
}
s32 ret = __osMotorAccess(PASS_RDRAM pfs, flag);
_return<s32>(ctx, 0);
_return<s32>(ctx, ret);
}
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx);
s32 channel = _arg<2, s32>(rdram, ctx);
MEM_W(8, pfs) = channel;
PTR(OSMesgQueue) mq = _arg<0, PTR(OSMesgQueue)>(rdram, ctx);
PTR(OSPfs) pfs = _arg<1, PTR(OSPfs)>(rdram, ctx);
int channel = _arg<2, s32>(rdram, ctx);
_return<s32>(ctx, 0);
s32 ret = osMotorInit(PASS_RDRAM mq, pfs, channel);
_return<s32>(ctx, ret);
}
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
s32 channel = MEM_W(8, pfs);
PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx);
// Only respect accesses to controller 0.
if (channel == 0) {
input_callbacks.set_rumble(true);
}
s32 ret = osMotorStart(PASS_RDRAM pfs);
_return<s32>(ctx, 0);
_return<s32>(ctx, ret);
}
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
s32 channel = MEM_W(8, pfs);
PTR(OSPfs) pfs = _arg<0, PTR(OSPfs)>(rdram, ctx);
// Only respect accesses to controller 0.
if (channel == 0) {
input_callbacks.set_rumble(false);
}
s32 ret = osMotorStop(PASS_RDRAM pfs);
_return<s32>(ctx, 0);
_return<s32>(ctx, ret);
}

View file

@ -379,8 +379,6 @@ bool ultramodern::is_game_started() {
return game_status.load() != GameStatus::None;
}
void set_input_callbacks(const ultramodern::input_callbacks_t& callback);
std::atomic_bool exited = false;
void ultramodern::quit() {
@ -392,7 +390,7 @@ void ultramodern::quit() {
current_game.reset();
}
void recomp::start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_, const ultramodern::events::callbacks_t& thread_callbacks_, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_) {
void recomp::start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input::callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_, const ultramodern::events::callbacks_t& thread_callbacks_, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_) {
recomp::check_all_stored_roms();
recomp::rsp::set_callbacks(rsp_callbacks);
@ -404,8 +402,6 @@ void recomp::start(ultramodern::WindowHandle window_handle, const recomp::rsp::c
ultramodern::set_callbacks(ultramodern_rsp_callbacks, audio_callbacks, input_callbacks, gfx_callbacks_, thread_callbacks_, error_handling_callbacks_);
set_input_callbacks(input_callbacks);
ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{};

View file

@ -13,6 +13,7 @@ add_library(ultramodern STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/audio.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/events.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/input.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.cpp"

View file

@ -0,0 +1,22 @@
#ifndef __ULTRAMODERN_INPUT_HPP__
#define __ULTRAMODERN_INPUT_HPP__
#include <cstdint>
namespace ultramodern {
namespace input {
struct callbacks_t {
using poll_input_t = void(void);
using get_input_t = void(uint16_t*, float*, float*);
using set_rumble_t = void(bool);
poll_input_t* poll_input;
get_input_t* get_input;
set_rumble_t* set_rumble;
};
void set_callbacks(const callbacks_t& callbacks);
}
}
#endif

View file

@ -214,6 +214,48 @@ typedef struct {
OSViFieldRegs fldRegs[2];
} OSViMode;
/*
* Structure for file system
*/
typedef struct {
int status;
PTR(OSMesgQueue) queue;
int channel;
u8 id[32]; // TODO: funky endianness here
u8 label[32]; // TODO: funky endianness here
int version;
int dir_size;
int inode_table; /* block location */
int minode_table; /* mirrioring inode_table */
int dir_table; /* block location */
int inode_start_page; /* page # */
// Padding and reversed members due to endianness
u8 padding[2];
u8 activebank;
u8 banks;
} OSPfs;
// Controller
typedef struct {
// These three members reversed due to endianness
u8 err_no;
u8 status; /* Controller status */
u16 type; /* Controller Type */
} OSContStatus;
typedef struct {
// These three members reversed due to endianness
s8 stick_y; /* -80 <= stick_y <= 80 */
s8 stick_x; /* -80 <= stick_x <= 80 */
u16 button;
// Padding due to endianness
u8 padding[3];
u8 err_no;
} OSContPad;
///////////////
// Functions //
///////////////
@ -255,6 +297,23 @@ int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval,
int osStopTimer(RDRAM_ARG PTR(OSTimer) timer);
u32 osVirtualToPhysical(PTR(void) addr);
/* Controller interface */
s32 osContInit(RDRAM_ARG PTR(OSMesgQueue), PTR(u8), PTR(OSContStatus));
s32 osContReset(RDRAM_ARG PTR(OSMesgQueue), PTR(OSContStatus));
s32 osContStartQuery(RDRAM_ARG PTR(OSMesgQueue));
s32 osContStartReadData(RDRAM_ARG PTR(OSMesgQueue));
s32 osContSetCh(RDRAM_ARG u8);
void osContGetQuery(RDRAM_ARG PTR(OSContStatus));
void osContGetReadData(RDRAM_ARG PTR(OSContPad));
/* Rumble PAK interface */
s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue), PTR(OSPfs), int);
s32 osMotorStop(RDRAM_ARG PTR(OSPfs));
s32 osMotorStart(RDRAM_ARG PTR(OSPfs));
s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs), s32);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -27,6 +27,7 @@
#include "ultramodern/error_handling.hpp"
#include "ultramodern/events.hpp"
#include "ultramodern/input.hpp"
#include "ultramodern/rsp.hpp"
struct UltraThreadContext {
@ -142,17 +143,6 @@ struct audio_callbacks_t {
set_frequency_t* set_frequency;
};
// TODO: These functions are currently called by librecomp, but will get called by ultramodern in the future
// Input
struct input_callbacks_t {
using poll_input_t = void(void);
using get_input_t = void(uint16_t*, float*, float*);
using set_rumble_t = void(bool);
poll_input_t* poll_input;
get_input_t* get_input;
set_rumble_t* set_rumble;
};
// TODO: Most of the members of this struct are not used by ultramodern. Should we move them to librecomp instead?
struct gfx_callbacks_t {
using gfx_data_t = void*;
@ -178,7 +168,7 @@ void set_audio_callbacks(const audio_callbacks_t& callbacks);
*
* It must be called only once and it must be called before `ultramodern::preinit`.
*/
void set_callbacks(const rsp::callbacks_t& rsp_callbacks, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks, const events::callbacks_t& events_callbacks, const error_handling::callbacks_t& error_handling_callbacks);
void set_callbacks(const rsp::callbacks_t& rsp_callbacks, const audio_callbacks_t& audio_callbacks, const input::callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks, const events::callbacks_t& events_callbacks, const error_handling::callbacks_t& error_handling_callbacks);
} // namespace ultramodern
#define MIN(a, b) ((a) < (b) ? (a) : (b))

144
ultramodern/src/input.cpp Normal file
View file

@ -0,0 +1,144 @@
#include <cassert>
#include "ultramodern/input.hpp"
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
static ultramodern::input::callbacks_t input_callbacks {};
void ultramodern::input::set_callbacks(const callbacks_t& callbacks) {
input_callbacks = callbacks;
}
static std::chrono::high_resolution_clock::time_point input_poll_time;
static void update_poll_time() {
input_poll_time = std::chrono::high_resolution_clock::now();
}
void ultramodern::measure_input_latency() {
#if 0
printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
#endif
}
#define MAXCONTROLLERS 4
static int max_controllers = 0;
/* Plain controller */
static void __osContGetInitData(u8* pattern, OSContStatus *data) {
// Set bit 0 to indicate that controller 0 is present
*pattern = 0x01;
// Mark controller 0 as present
data[0].type = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
data[0].status = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
data[0].err_no = 0x00; // errno: 0 (from libultra)
// Mark controllers 1-3 as not connected
for (int controller = 1; controller < max_controllers; controller++) {
// Libultra doesn't write status or type for absent controllers
data[controller].err_no = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
}
extern "C" s32 osContInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(u8) bitpattern_, PTR(OSContStatus) data_) {
u8 *bitpattern = TO_PTR(u8, bitpattern_);
OSContStatus *data = TO_PTR(OSContStatus, data_);
max_controllers = MAXCONTROLLERS;
__osContGetInitData(bitpattern, data);
return 0;
}
extern "C" s32 osContReset(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSContStatus) data) {
assert(false);
return 0;
}
extern "C" s32 osContStartQuery(RDRAM_ARG PTR(OSMesgQueue) mq) {
ultramodern::send_si_message(PASS_RDRAM1);
return 0;
}
extern "C" s32 osContStartReadData(RDRAM_ARG PTR(OSMesgQueue) mq) {
if (input_callbacks.poll_input != nullptr) {
input_callbacks.poll_input();
}
update_poll_time();
ultramodern::send_si_message(rdram);
return 0;
}
extern "C" s32 osContSetCh(RDRAM_ARG u8 ch) {
max_controllers = std::min(ch, u8(MAXCONTROLLERS));
return 0;
}
extern "C" void osContGetQuery(RDRAM_ARG PTR(OSContStatus) data_) {
OSContStatus *data = TO_PTR(OSContStatus, data_);
u8 pattern;
__osContGetInitData(&pattern, data);
}
extern "C" void osContGetReadData(RDRAM_ARG PTR(OSContPad) data_) {
OSContPad *data = TO_PTR(OSContPad, data_);
uint16_t buttons = 0;
float x = 0.0f;
float y = 0.0f;
if (input_callbacks.get_input != nullptr) {
input_callbacks.get_input(&buttons, &x, &y);
}
if (max_controllers > 0) {
// button
data[0].button = buttons;
data[0].stick_x = (int8_t)(127 * x);
data[0].stick_y = (int8_t)(127 * y);
data[0].err_no = 0;
}
for (int controller = 1; controller < max_controllers; controller++) {
data[controller].err_no = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
}
/* Rumble */
s32 osMotorInit(RDRAM_ARG PTR(OSMesgQueue) mq, PTR(OSPfs) pfs_, int channel) {
OSPfs *pfs = TO_PTR(OSPfs, pfs_);
pfs->channel = channel;
return 0;
}
s32 osMotorStop(RDRAM_ARG PTR(OSPfs) pfs) {
return __osMotorAccess(PASS_RDRAM pfs, false);
}
s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) {
return __osMotorAccess(PASS_RDRAM pfs, true);
}
s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) {
OSPfs *pfs = TO_PTR(OSPfs, pfs_);
// Only respect accesses to controller 0.
if (pfs->channel == 0) {
if (input_callbacks.set_rumble != nullptr) {
input_callbacks.set_rumble(flag);
}
}
return 0;
}

View file

@ -4,14 +4,14 @@
void ultramodern::set_callbacks(
const rsp::callbacks_t& rsp_callbacks,
const audio_callbacks_t& audio_callbacks,
const input_callbacks_t& input_callbacks,
const input::callbacks_t& input_callbacks,
const gfx_callbacks_t& gfx_callbacks,
const events::callbacks_t& thread_callbacks,
const error_handling::callbacks_t& error_handling_callbacks
) {
ultramodern::rsp::set_callbacks(rsp_callbacks);
ultramodern::set_audio_callbacks(audio_callbacks);
(void)input_callbacks; // nothing yet
ultramodern::input::set_callbacks(input_callbacks);
(void)gfx_callbacks; // nothing yet
ultramodern::events::set_callbacks(thread_callbacks);
ultramodern::error_handling::set_callbacks(error_handling_callbacks);