mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-11-25 22:24:20 +01:00
More WIP linux work, upgraded libultra to include changes from BT recomp
This commit is contained in:
parent
2865ef758e
commit
f361fddd3e
@ -46,8 +46,8 @@ typedef uint64_t gpr;
|
|||||||
//(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
//(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
||||||
|
|
||||||
#define SD(val, offset, reg) { \
|
#define SD(val, offset, reg) { \
|
||||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((val) >> 0); \
|
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 0); \
|
||||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((val) >> 32); \
|
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 32); \
|
||||||
}
|
}
|
||||||
|
|
||||||
//#define SD(val, offset, reg) { \
|
//#define SD(val, offset, reg) { \
|
||||||
@ -239,15 +239,26 @@ typedef struct {
|
|||||||
r8, r9, r10, r11, r12, r13, r14, r15,
|
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||||
r16, r17, r18, r19, r20, r21, r22, r23,
|
r16, r17, r18, r19, r20, r21, r22, r23,
|
||||||
r24, r25, r26, r27, r28, r29, r30, r31;
|
r24, r25, r26, r27, r28, r29, r30, r31;
|
||||||
fpr f0, f2, f4, f6, f8, f10, f12, f14,
|
fpr f0, f1, f2, f3, f4, f5, f6, f7,
|
||||||
f16, f18, f20, f22, f24, f26, f28, f30;
|
f8, f9, f10, f11, f12, f13, f14, f15,
|
||||||
|
f16, f17, f18, f19, f20, f21, f22, f23,
|
||||||
|
f24, f25, f26, f27, f28, f29, f30, f31;
|
||||||
uint64_t hi, lo;
|
uint64_t hi, lo;
|
||||||
|
uint32_t* f_odd;
|
||||||
|
uint32_t status_reg;
|
||||||
|
uint8_t mips3_float_mode;
|
||||||
} recomp_context;
|
} recomp_context;
|
||||||
|
|
||||||
|
// Checks if the target is an even float register or that mips3 float mode is enabled
|
||||||
|
#define CHECK_FR(ctx, idx) \
|
||||||
|
assert(((idx) & 1) == 0 || (ctx)->mips3_float_mode)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void cop0_status_write(recomp_context* ctx, gpr value);
|
||||||
|
gpr cop0_status_read(recomp_context* ctx);
|
||||||
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
|
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
|
||||||
void do_break(uint32_t vram);
|
void do_break(uint32_t vram);
|
||||||
|
|
||||||
@ -272,6 +283,9 @@ extern int32_t section_addresses[];
|
|||||||
#define RELOC_LO16(section_index, offset) \
|
#define RELOC_LO16(section_index, offset) \
|
||||||
LO16(section_addresses[section_index] + (offset))
|
LO16(section_addresses[section_index] + (offset))
|
||||||
|
|
||||||
|
// For Banjo-Tooie
|
||||||
|
void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||||
|
|
||||||
// For the Mario Party games (not working)
|
// For the Mario Party games (not working)
|
||||||
//// This has to be in this file so it can be inlined
|
//// This has to be in this file so it can be inlined
|
||||||
//struct jmp_buf_storage {
|
//struct jmp_buf_storage {
|
||||||
|
@ -8,7 +8,8 @@ enum class RspExitReason {
|
|||||||
Invalid,
|
Invalid,
|
||||||
Broke,
|
Broke,
|
||||||
ImemOverrun,
|
ImemOverrun,
|
||||||
UnhandledJumpTarget
|
UnhandledJumpTarget,
|
||||||
|
Unsupported
|
||||||
};
|
};
|
||||||
|
|
||||||
extern uint8_t dmem[];
|
extern uint8_t dmem[];
|
||||||
@ -16,19 +17,19 @@ extern uint16_t rspReciprocals[512];
|
|||||||
extern uint16_t rspInverseSquareRoots[512];
|
extern uint16_t rspInverseSquareRoots[512];
|
||||||
|
|
||||||
#define RSP_MEM_W(offset, addr) \
|
#define RSP_MEM_W(offset, addr) \
|
||||||
(*reinterpret_cast<uint32_t*>(dmem + (offset) + (addr)))
|
(*reinterpret_cast<uint32_t*>(dmem + (0xFFF & ((offset) + (addr)))))
|
||||||
|
|
||||||
#define RSP_MEM_H(offset, addr) \
|
#define RSP_MEM_H(offset, addr) \
|
||||||
(*reinterpret_cast<int16_t*>(dmem + (((offset) + (addr)) ^ 2)))
|
(*reinterpret_cast<int16_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 2))))
|
||||||
|
|
||||||
#define RSP_MEM_HU(offset, addr) \
|
#define RSP_MEM_HU(offset, addr) \
|
||||||
(*reinterpret_cast<uint16_t*>(dmem + (((offset) + (addr)) ^ 2)))
|
(*reinterpret_cast<uint16_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 2))))
|
||||||
|
|
||||||
#define RSP_MEM_B(offset, addr) \
|
#define RSP_MEM_B(offset, addr) \
|
||||||
(*reinterpret_cast<int8_t*>(dmem + (((offset) + (addr)) ^ 3)))
|
(*reinterpret_cast<int8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||||
|
|
||||||
#define RSP_MEM_BU(offset, addr) \
|
#define RSP_MEM_BU(offset, addr) \
|
||||||
(*reinterpret_cast<uint8_t*>(dmem + (((offset) + (addr)) ^ 3)))
|
(*reinterpret_cast<uint8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||||
|
|
||||||
#define RSP_ADD32(a, b) \
|
#define RSP_ADD32(a, b) \
|
||||||
((int32_t)((a) + (b)))
|
((int32_t)((a) + (b)))
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#ifndef __RT64_LAYER_H__
|
#ifndef __RT64_LAYER_H__
|
||||||
#define __RT64_LAYER_H__
|
#define __RT64_LAYER_H__
|
||||||
|
|
||||||
typedef struct {
|
#include "../portultra/multilibultra.hpp"
|
||||||
void* hWnd;
|
|
||||||
void* hStatusBar;
|
|
||||||
|
|
||||||
int Reserved;
|
typedef struct {
|
||||||
|
// void* hWnd;
|
||||||
|
// void* hStatusBar;
|
||||||
|
|
||||||
|
// int Reserved;
|
||||||
|
|
||||||
unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */
|
unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */
|
||||||
unsigned char* RDRAM;
|
unsigned char* RDRAM;
|
||||||
@ -40,9 +42,9 @@ typedef struct {
|
|||||||
|
|
||||||
void (*CheckInterrupts)(void);
|
void (*CheckInterrupts)(void);
|
||||||
|
|
||||||
// unsigned int version;
|
unsigned int version;
|
||||||
// unsigned int* SP_STATUS_REG;
|
unsigned int* SP_STATUS_REG;
|
||||||
// const unsigned int* RDRAM_SIZE;
|
const unsigned int* RDRAM_SIZE;
|
||||||
} GFX_INFO;
|
} GFX_INFO;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -61,7 +63,7 @@ typedef struct {
|
|||||||
//DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr;
|
//DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr;
|
||||||
//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
|
//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
|
||||||
|
|
||||||
DLLIMPORT int InitiateGFX(GFX_INFO Gfx_Info);
|
extern "C" int InitiateGFXLinux(GFX_INFO Gfx_Info, Window window, Display *display);
|
||||||
DLLIMPORT void ProcessRDPList(void);
|
DLLIMPORT void ProcessRDPList(void);
|
||||||
DLLIMPORT void ProcessDList(void);
|
DLLIMPORT void ProcessDList(void);
|
||||||
DLLIMPORT void UpdateScreen(void);
|
DLLIMPORT void UpdateScreen(void);
|
||||||
|
@ -64,8 +64,8 @@ float buffer_offset_frames = 0.5f;
|
|||||||
uint32_t Multilibultra::get_remaining_audio_bytes() {
|
uint32_t Multilibultra::get_remaining_audio_bytes() {
|
||||||
// Get the number of remaining buffered audio bytes.
|
// Get the number of remaining buffered audio bytes.
|
||||||
uint32_t buffered_byte_count;
|
uint32_t buffered_byte_count;
|
||||||
if (audio_callbacks.get_samples_remaining()) {
|
if (audio_callbacks.get_frames_remaining != nullptr) {
|
||||||
buffered_byte_count = audio_callbacks.get_samples_remaining() * sizeof(int16_t);
|
buffered_byte_count = audio_callbacks.get_frames_remaining() * 2 * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buffered_byte_count = 100;
|
buffered_byte_count = 100;
|
||||||
|
@ -93,6 +93,8 @@ extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 ret
|
|||||||
events_context.vi.retrace_count = retrace_count;
|
events_context.vi.retrace_count = retrace_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t total_vis = 0;
|
||||||
|
|
||||||
void vi_thread_func() {
|
void vi_thread_func() {
|
||||||
Multilibultra::set_native_thread_name("VI Thread");
|
Multilibultra::set_native_thread_name("VI Thread");
|
||||||
// This thread should be prioritized over every other thread in the application, as it's what allows
|
// This thread should be prioritized over every other thread in the application, as it's what allows
|
||||||
@ -100,7 +102,6 @@ void vi_thread_func() {
|
|||||||
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::Critical);
|
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::Critical);
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
uint64_t total_vis = 0;
|
|
||||||
int remaining_retraces = events_context.vi.retrace_count;
|
int remaining_retraces = events_context.vi.retrace_count;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -157,7 +158,7 @@ void dp_complete() {
|
|||||||
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
|
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle);
|
void RT64Init(uint8_t* rom, uint8_t* rdram, Multilibultra::WindowHandle window_handle);
|
||||||
void RT64SendDL(uint8_t* rdram, const OSTask* task);
|
void RT64SendDL(uint8_t* rdram, const OSTask* task);
|
||||||
void RT64UpdateScreen(uint32_t vi_origin);
|
void RT64UpdateScreen(uint32_t vi_origin);
|
||||||
void RT64ChangeWindow();
|
void RT64ChangeWindow();
|
||||||
@ -235,12 +236,29 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, void* window_handle) {
|
static Multilibultra::gfx_callbacks_t gfx_callbacks;
|
||||||
|
|
||||||
|
void Multilibultra::set_gfx_callbacks(const gfx_callbacks_t* callbacks) {
|
||||||
|
if (callbacks != nullptr) {
|
||||||
|
gfx_callbacks = *callbacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, Multilibultra::WindowHandle window_handle) {
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
Multilibultra::gfx_callbacks_t::gfx_data_t gfx_data{};
|
||||||
|
|
||||||
Multilibultra::set_native_thread_name("Gfx Thread");
|
Multilibultra::set_native_thread_name("Gfx Thread");
|
||||||
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::Normal);
|
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::Normal);
|
||||||
|
|
||||||
|
if (gfx_callbacks.create_gfx != nullptr) {
|
||||||
|
gfx_data = gfx_callbacks.create_gfx();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gfx_callbacks.create_window != nullptr) {
|
||||||
|
window_handle = gfx_callbacks.create_window(gfx_data);
|
||||||
|
}
|
||||||
|
|
||||||
RT64Init(rom, rdram, window_handle);
|
RT64Init(rom, rdram, window_handle);
|
||||||
|
|
||||||
rsp_constants_init();
|
rsp_constants_init();
|
||||||
@ -263,15 +281,13 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read
|
|||||||
RT64SendDL(rdram, &task_action->task);
|
RT64SendDL(rdram, &task_action->task);
|
||||||
dp_complete();
|
dp_complete();
|
||||||
} else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
|
} else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
|
||||||
static volatile int i = 0;
|
|
||||||
if (i >= 100) {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
events_context.vi.current_buffer = events_context.vi.next_buffer;
|
events_context.vi.current_buffer = events_context.vi.next_buffer;
|
||||||
RT64UpdateScreen(swap_action->origin);
|
RT64UpdateScreen(swap_action->origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (gfx_callbacks.update_gfx != nullptr) {
|
||||||
|
gfx_callbacks.update_gfx(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,7 +444,7 @@ void Multilibultra::send_si_message() {
|
|||||||
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
|
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom, void* window_handle) {
|
void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom, Multilibultra::WindowHandle window_handle) {
|
||||||
std::atomic_flag gfx_thread_ready;
|
std::atomic_flag gfx_thread_ready;
|
||||||
std::atomic_flag task_thread_ready;
|
std::atomic_flag task_thread_ready;
|
||||||
events_context.rdram = rdram;
|
events_context.rdram = rdram;
|
||||||
|
@ -5,6 +5,58 @@
|
|||||||
#include "multilibultra.hpp"
|
#include "multilibultra.hpp"
|
||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
|
|
||||||
|
#if defined(_M_X64)
|
||||||
|
static inline void spinlock_pause() {
|
||||||
|
_mm_pause();
|
||||||
|
}
|
||||||
|
#elif defined(__x86_64__)
|
||||||
|
static inline void spinlock_pause() {
|
||||||
|
__builtin_ia32_pause();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "No spinlock_pause implementation for current architecture"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class atomic_spinlock {
|
||||||
|
static_assert(sizeof(std::atomic<T>) == sizeof(T), "atomic_spinlock must be used with a type that is the same size as its atomic counterpart");
|
||||||
|
static_assert(std::atomic<T>::is_always_lock_free, "atomic_spinlock must be used with an always lock-free atomic type");
|
||||||
|
std::atomic_ref<T> locked_;
|
||||||
|
public:
|
||||||
|
atomic_spinlock(T& flag) : locked_{ flag } {}
|
||||||
|
|
||||||
|
void lock() {
|
||||||
|
// Loop until the lock is acquired.
|
||||||
|
while (true) {
|
||||||
|
// Try to acquire the lock.
|
||||||
|
if (!locked_.exchange(true, std::memory_order_acquire)) {
|
||||||
|
// If it was acquired then exit the loop.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Otherwise, wait until the lock is no longer acquired.
|
||||||
|
// Doing this instead of constantly trying to acquire the lock reduces cache coherency traffic.
|
||||||
|
while (locked_.load(std::memory_order_relaxed)) {
|
||||||
|
// Add a platform-specific pause instruction to reduce load unit traffic.
|
||||||
|
spinlock_pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
// Release the lock by setting it to false.
|
||||||
|
locked_.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class mesg_queue_lock {
|
||||||
|
OSMesgQueue* queue_;
|
||||||
|
atomic_spinlock<uint8_t> spinlock_;
|
||||||
|
public:
|
||||||
|
mesg_queue_lock(OSMesgQueue* mq) : queue_{ mq }, spinlock_{ mq->lock } {}
|
||||||
|
void lock() { spinlock_.lock(); }
|
||||||
|
void unlock() { spinlock_.unlock(); }
|
||||||
|
};
|
||||||
|
|
||||||
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
|
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
|
||||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||||
mq->blocked_on_recv = NULLPTR;
|
mq->blocked_on_recv = NULLPTR;
|
||||||
@ -13,6 +65,7 @@ extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) ms
|
|||||||
mq->msg = msg;
|
mq->msg = msg;
|
||||||
mq->validCount = 0;
|
mq->validCount = 0;
|
||||||
mq->first = 0;
|
mq->first = 0;
|
||||||
|
mq->lock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
|
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
|
||||||
@ -47,148 +100,159 @@ bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) {
|
|||||||
return *queue == NULLPTR;
|
return *queue == NULLPTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
std::mutex test_mutex{};
|
||||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
|
||||||
|
|
||||||
// Prevent accidentally blocking anything that isn't a game thread
|
// Attempts to put a message into a queue.
|
||||||
if (!Multilibultra::is_game_thread()) {
|
// If the queue is not full, returns true and pops a thread from the blocked on receive list.
|
||||||
flags = OS_MESG_NOBLOCK;
|
// If the queue is full and this is a blocking send, places the current thread into the blocked on send list
|
||||||
}
|
// for the message queue, marks the current thread as being blocked on a queue and returns false.
|
||||||
|
bool mesg_queue_try_insert(RDRAM_ARG OSMesgQueue* mq, OSMesg msg, OSThread*& to_run, bool jam, bool blocking) {
|
||||||
|
//mesg_queue_lock lock{ mq };
|
||||||
|
std::lock_guard guard{ test_mutex };
|
||||||
|
|
||||||
Multilibultra::disable_preemption();
|
// If the queue is full, insert this thread into the blocked on send queue and return false.
|
||||||
|
if (MQ_IS_FULL(mq)) {
|
||||||
if (flags == OS_MESG_NOBLOCK) {
|
if (blocking) {
|
||||||
// If non-blocking, fail if the queue is full
|
|
||||||
if (MQ_IS_FULL(mq)) {
|
|
||||||
Multilibultra::enable_preemption();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, yield this thread until the queue has room
|
|
||||||
while (MQ_IS_FULL(mq)) {
|
|
||||||
debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
|
||||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
|
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
|
||||||
Multilibultra::enable_preemption();
|
// TODO is it safe to use the schedule queue here while in the message queue lock?
|
||||||
Multilibultra::pause_self(PASS_RDRAM1);
|
Multilibultra::block_self(PASS_RDRAM1);
|
||||||
Multilibultra::disable_preemption();
|
|
||||||
}
|
}
|
||||||
|
to_run = nullptr;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 last = (mq->first + mq->validCount) % mq->msgCount;
|
// The queue wasn't full, so place the message into it.
|
||||||
TO_PTR(OSMesg, mq->msg)[last] = msg;
|
if (jam) {
|
||||||
mq->validCount++;
|
// Insert this message at the start of the queue.
|
||||||
|
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
|
||||||
OSThread* to_run = nullptr;
|
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
|
||||||
|
mq->validCount++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Insert this message at the end of the queue.
|
||||||
|
s32 last = (mq->first + mq->validCount) % mq->msgCount;
|
||||||
|
TO_PTR(OSMesg, mq->msg)[last] = msg;
|
||||||
|
mq->validCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop a thread from the blocked on recv queue to wake afterwards.
|
||||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
|
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
|
||||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
|
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
|
||||||
}
|
}
|
||||||
|
|
||||||
Multilibultra::enable_preemption();
|
return true;
|
||||||
if (to_run) {
|
|
||||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
|
||||||
if (Multilibultra::is_game_thread()) {
|
|
||||||
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
|
|
||||||
if (to_run->priority > self->priority) {
|
|
||||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
|
||||||
} else {
|
|
||||||
Multilibultra::schedule_running_thread(to_run);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Multilibultra::schedule_running_thread(to_run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
// Attempts to remove a message from a queue.
|
||||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
// If the queue is not empty, returns true and pops a thread from the blocked on send list.
|
||||||
Multilibultra::disable_preemption();
|
// If the queue is empty and this is a blocking receive, places the current thread into the blocked on receive list
|
||||||
|
// for the message queue, marks the current thread as being blocked on a queue and returns false.
|
||||||
|
bool mesg_queue_try_remove(RDRAM_ARG OSMesgQueue* mq, PTR(OSMesg) msg_out, OSThread*& to_run, bool blocking) {
|
||||||
|
//mesg_queue_lock lock{ mq };
|
||||||
|
std::lock_guard guard{ test_mutex };
|
||||||
|
|
||||||
if (flags == OS_MESG_NOBLOCK) {
|
// If the queue is full, insert this thread into the blocked on receive queue and return false.
|
||||||
// If non-blocking, fail if the queue is full
|
if (MQ_IS_EMPTY(mq)) {
|
||||||
if (MQ_IS_FULL(mq)) {
|
if (blocking) {
|
||||||
Multilibultra::enable_preemption();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
|
||||||
while (MQ_IS_FULL(mq)) {
|
|
||||||
debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
|
||||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
|
|
||||||
Multilibultra::enable_preemption();
|
|
||||||
Multilibultra::pause_self(PASS_RDRAM1);
|
|
||||||
Multilibultra::disable_preemption();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
|
|
||||||
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
|
|
||||||
mq->validCount++;
|
|
||||||
|
|
||||||
OSThread *to_run = nullptr;
|
|
||||||
|
|
||||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
|
|
||||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
|
|
||||||
}
|
|
||||||
|
|
||||||
Multilibultra::enable_preemption();
|
|
||||||
if (to_run) {
|
|
||||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
|
||||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
|
||||||
if (to_run->priority > self->priority) {
|
|
||||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
|
||||||
} else {
|
|
||||||
Multilibultra::schedule_running_thread(to_run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
|
|
||||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
|
||||||
OSMesg *msg = TO_PTR(OSMesg, msg_);
|
|
||||||
Multilibultra::disable_preemption();
|
|
||||||
|
|
||||||
if (flags == OS_MESG_NOBLOCK) {
|
|
||||||
// If non-blocking, fail if the queue is empty
|
|
||||||
if (MQ_IS_EMPTY(mq)) {
|
|
||||||
Multilibultra::enable_preemption();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
|
||||||
while (MQ_IS_EMPTY(mq)) {
|
|
||||||
debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
|
||||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread());
|
thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread());
|
||||||
Multilibultra::enable_preemption();
|
// TODO is it safe to use the schedule queue here while in the message queue lock?
|
||||||
Multilibultra::pause_self(PASS_RDRAM1);
|
Multilibultra::block_self(PASS_RDRAM1);
|
||||||
Multilibultra::disable_preemption();
|
|
||||||
}
|
}
|
||||||
|
to_run = nullptr;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg_ != NULLPTR) {
|
// The queue wasn't empty, so remove the first message from it.
|
||||||
*msg = TO_PTR(OSMesg, mq->msg)[mq->first];
|
if (msg_out != NULLPTR) {
|
||||||
|
*TO_PTR(OSMesg, msg_out) = TO_PTR(OSMesg, mq->msg)[mq->first];
|
||||||
}
|
}
|
||||||
|
|
||||||
mq->first = (mq->first + 1) % mq->msgCount;
|
mq->first = (mq->first + 1) % mq->msgCount;
|
||||||
mq->validCount--;
|
mq->validCount--;
|
||||||
|
|
||||||
OSThread *to_run = nullptr;
|
// Pop a thread from the blocked on send queue to wake afterwards.
|
||||||
|
|
||||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) {
|
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) {
|
||||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send);
|
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send);
|
||||||
}
|
}
|
||||||
|
|
||||||
Multilibultra::enable_preemption();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MesgQueueActionType {
|
||||||
|
Send,
|
||||||
|
Jam,
|
||||||
|
Receive
|
||||||
|
};
|
||||||
|
|
||||||
|
s32 mesg_queue_action(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, PTR(OSMesg) msg_out, s32 flags, MesgQueueActionType action) {
|
||||||
|
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||||
|
OSThread* this_thread = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||||
|
bool is_blocking = flags != OS_MESG_NOBLOCK;
|
||||||
|
|
||||||
|
// Prevent accidentally blocking anything that isn't a game thread
|
||||||
|
if (!Multilibultra::is_game_thread()) {
|
||||||
|
is_blocking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSThread* to_run = nullptr;
|
||||||
|
|
||||||
|
// Repeatedly attempt to send the message until it's successful.
|
||||||
|
while (true) {
|
||||||
|
// Try to insert/remove the message into the queue depending on the action.
|
||||||
|
bool success = false;
|
||||||
|
switch (action) {
|
||||||
|
case MesgQueueActionType::Send:
|
||||||
|
success = mesg_queue_try_insert(PASS_RDRAM mq, msg, to_run, false, is_blocking);
|
||||||
|
break;
|
||||||
|
case MesgQueueActionType::Jam:
|
||||||
|
success = mesg_queue_try_insert(PASS_RDRAM mq, msg, to_run, true, is_blocking);
|
||||||
|
break;
|
||||||
|
case MesgQueueActionType::Receive:
|
||||||
|
success = mesg_queue_try_remove(PASS_RDRAM mq, msg_out, to_run, is_blocking);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If successful, don't block.
|
||||||
|
if (success) {
|
||||||
|
//goto after;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise if the action was unsuccessful but wasn't blocking, return -1 to indicate a failure.
|
||||||
|
if (!is_blocking) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The action failed, so pause this thread until unblocked by the queue.
|
||||||
|
debug_printf("[Message Queue] Thread %d is blocked on %s\n", this_thread->id, action == MesgQueueActionType::Receive ? "receive" : "send");
|
||||||
|
|
||||||
|
// Wait for it this thread be resumed.
|
||||||
|
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||||
|
}
|
||||||
|
//after:
|
||||||
|
|
||||||
|
// If any thread was blocked on receiving from this queue, wake it.
|
||||||
if (to_run) {
|
if (to_run) {
|
||||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
||||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
Multilibultra::unblock_thread(to_run);
|
||||||
if (to_run->priority > self->priority) {
|
|
||||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
// If the unblocked thread is higher priority than this one, pause this thread so it can take over.
|
||||||
} else {
|
if (Multilibultra::is_game_thread() && to_run->priority > this_thread->priority) {
|
||||||
Multilibultra::schedule_running_thread(to_run);
|
Multilibultra::yield_self(PASS_RDRAM1);
|
||||||
|
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||||
|
return mesg_queue_action(PASS_RDRAM mq_, msg, NULLPTR, flags, MesgQueueActionType::Send);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||||
|
return mesg_queue_action(PASS_RDRAM mq_, msg, NULLPTR, flags, MesgQueueActionType::Jam);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_out_, s32 flags) {
|
||||||
|
return mesg_queue_action(PASS_RDRAM mq_, NULLPTR, msg_out_, flags, MesgQueueActionType::Receive);
|
||||||
|
}
|
||||||
|
@ -8,31 +8,57 @@
|
|||||||
|
|
||||||
#include "ultra64.h"
|
#include "ultra64.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# include <Windows.h>
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
# include "android/native_window.h"
|
||||||
|
#elif defined(__linux__)
|
||||||
|
# include "X11/Xlib.h"
|
||||||
|
# undef None
|
||||||
|
# undef Status
|
||||||
|
# undef LockMask
|
||||||
|
#endif
|
||||||
|
|
||||||
struct UltraThreadContext {
|
struct UltraThreadContext {
|
||||||
std::thread host_thread;
|
std::thread host_thread;
|
||||||
std::atomic_bool running;
|
std::atomic_bool scheduled;
|
||||||
|
std::atomic_bool descheduled;
|
||||||
std::atomic_bool initialized;
|
std::atomic_bool initialized;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Multilibultra {
|
namespace Multilibultra {
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Native HWND handle to the target window.
|
||||||
|
using WindowHandle = HWND;
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
using WindowHandle = ANativeWindow*;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
struct WindowHandle {
|
||||||
|
Display* display;
|
||||||
|
Window window;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram
|
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram
|
||||||
constexpr int32_t cart_handle = 0x80800000;
|
constexpr int32_t cart_handle = 0x80800000;
|
||||||
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
|
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
|
||||||
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
|
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
|
||||||
|
|
||||||
void preinit(uint8_t* rdram, uint8_t* rom, void* window_handle);
|
void preinit(uint8_t* rdram, uint8_t* rom, WindowHandle window_handle);
|
||||||
void save_init();
|
void save_init();
|
||||||
void init_scheduler();
|
void init_scheduler();
|
||||||
void init_events(uint8_t* rdram, uint8_t* rom, void* window_handle);
|
void init_events(uint8_t* rdram, uint8_t* rom, WindowHandle window_handle);
|
||||||
void init_timers(RDRAM_ARG1);
|
void init_timers(RDRAM_ARG1);
|
||||||
void set_self_paused(RDRAM_ARG1);
|
void yield_self(RDRAM_ARG1);
|
||||||
|
void block_self(RDRAM_ARG1);
|
||||||
|
void unblock_thread(OSThread* t);
|
||||||
void wait_for_resumed(RDRAM_ARG1);
|
void wait_for_resumed(RDRAM_ARG1);
|
||||||
void swap_to_thread(RDRAM_ARG OSThread *to);
|
void swap_to_thread(RDRAM_ARG OSThread *to);
|
||||||
void pause_thread_impl(OSThread *t);
|
void resume_thread_impl(OSThread* t);
|
||||||
void resume_thread_impl(OSThread *t);
|
|
||||||
void schedule_running_thread(OSThread *t);
|
void schedule_running_thread(OSThread *t);
|
||||||
void pause_self(RDRAM_ARG1);
|
void halt_self(RDRAM_ARG1);
|
||||||
|
void stop_thread(OSThread *t);
|
||||||
void cleanup_thread(OSThread *t);
|
void cleanup_thread(OSThread *t);
|
||||||
|
|
||||||
enum class ThreadPriority {
|
enum class ThreadPriority {
|
||||||
@ -46,8 +72,6 @@ enum class ThreadPriority {
|
|||||||
void set_native_thread_name(const std::string& name);
|
void set_native_thread_name(const std::string& name);
|
||||||
void set_native_thread_priority(ThreadPriority pri);
|
void set_native_thread_priority(ThreadPriority pri);
|
||||||
PTR(OSThread) this_thread();
|
PTR(OSThread) this_thread();
|
||||||
void disable_preemption();
|
|
||||||
void enable_preemption();
|
|
||||||
void notify_scheduler();
|
void notify_scheduler();
|
||||||
void reprioritize_thread(OSThread *t, OSPri pri);
|
void reprioritize_thread(OSThread *t, OSPri pri);
|
||||||
void set_main_thread();
|
void set_main_thread();
|
||||||
@ -69,7 +93,7 @@ struct audio_callbacks_t {
|
|||||||
using get_samples_remaining_t = size_t();
|
using get_samples_remaining_t = size_t();
|
||||||
using set_frequency_t = void(uint32_t);
|
using set_frequency_t = void(uint32_t);
|
||||||
queue_samples_t* queue_samples;
|
queue_samples_t* queue_samples;
|
||||||
get_samples_remaining_t* get_samples_remaining;
|
get_samples_remaining_t* get_frames_remaining;
|
||||||
set_frequency_t* set_frequency;
|
set_frequency_t* set_frequency;
|
||||||
};
|
};
|
||||||
void set_audio_callbacks(const audio_callbacks_t* callbacks);
|
void set_audio_callbacks(const audio_callbacks_t* callbacks);
|
||||||
@ -81,19 +105,22 @@ struct input_callbacks_t {
|
|||||||
};
|
};
|
||||||
void set_input_callbacks(const input_callbacks_t* callback);
|
void set_input_callbacks(const input_callbacks_t* callback);
|
||||||
|
|
||||||
class preemption_guard {
|
struct gfx_callbacks_t {
|
||||||
public:
|
using gfx_data_t = void*;
|
||||||
preemption_guard();
|
using create_gfx_t = gfx_data_t();
|
||||||
~preemption_guard();
|
using create_window_t = WindowHandle(gfx_data_t);
|
||||||
private:
|
using update_gfx_t = void(gfx_data_t);
|
||||||
std::lock_guard<std::mutex> lock;
|
create_gfx_t* create_gfx;
|
||||||
|
create_window_t* create_window;
|
||||||
|
update_gfx_t* update_gfx;
|
||||||
};
|
};
|
||||||
|
void set_gfx_callbacks(const gfx_callbacks_t* callbacks);
|
||||||
|
|
||||||
} // namespace Multilibultra
|
} // namespace Multilibultra
|
||||||
|
|
||||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
|
||||||
#define debug_printf(...)
|
//#define debug_printf(...)
|
||||||
//#define debug_printf(...) printf(__VA_ARGS__);
|
#define debug_printf(...) printf(__VA_ARGS__);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "blockingconcurrentqueue.h"
|
#include "blockingconcurrentqueue.h"
|
||||||
#include "multilibultra.hpp"
|
#include "multilibultra.hpp"
|
||||||
@ -24,17 +25,27 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == this->c.begin()) {
|
// remove element and re-heap
|
||||||
// deque the top element
|
this->c.erase(it);
|
||||||
this->pop();
|
std::make_heap(this->c.begin(), this->c.end(), this->comp);
|
||||||
} else {
|
|
||||||
// remove element and re-heap
|
|
||||||
this->c.erase(it);
|
|
||||||
std::make_heap(this->c.begin(), this->c.end(), this->comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print() {
|
||||||
|
std::vector<OSThread*> backup = this->c;
|
||||||
|
debug_printf("[Scheduler] Scheduled Threads:\n");
|
||||||
|
while (!empty()) {
|
||||||
|
OSThread* t = top();
|
||||||
|
pop();
|
||||||
|
debug_printf(" %d: pri %d state %d\n", t->id, t->priority, t->state);
|
||||||
|
}
|
||||||
|
this->c = backup;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(OSThread* t) {
|
||||||
|
return std::find(this->c.begin(), this->c.end(), t) != this->c.end();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NotifySchedulerAction {
|
struct NotifySchedulerAction {
|
||||||
@ -58,26 +69,54 @@ struct ReprioritizeThreadAction {
|
|||||||
OSPri pri;
|
OSPri pri;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ThreadAction = std::variant<NotifySchedulerAction, ScheduleThreadAction, StopThreadAction, CleanupThreadAction, ReprioritizeThreadAction>;
|
struct YieldedThreadAction {
|
||||||
|
OSThread* t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlockedThreadAction {
|
||||||
|
OSThread* t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnblockThreadAction {
|
||||||
|
OSThread* t;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ThreadAction = std::variant<std::monostate, NotifySchedulerAction, ScheduleThreadAction, StopThreadAction, CleanupThreadAction, ReprioritizeThreadAction, YieldedThreadAction, BlockedThreadAction, UnblockThreadAction>;
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
moodycamel::BlockingConcurrentQueue<ThreadAction> action_queue{};
|
moodycamel::BlockingConcurrentQueue<ThreadAction> action_queue{};
|
||||||
OSThread* running_thread;
|
OSThread* running_thread;
|
||||||
|
|
||||||
bool can_preempt;
|
|
||||||
std::mutex premption_mutex;
|
|
||||||
} scheduler_context{};
|
} scheduler_context{};
|
||||||
|
|
||||||
void handle_thread_queueing(thread_queue_t& running_thread_queue, const ScheduleThreadAction& action) {
|
void handle_thread_queueing(thread_queue_t& running_thread_queue, const ScheduleThreadAction& action) {
|
||||||
OSThread* to_schedule = action.t;
|
OSThread* to_schedule = action.t;
|
||||||
debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id);
|
debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id);
|
||||||
running_thread_queue.push(to_schedule);
|
|
||||||
|
// Do not schedule the thread if it's waiting on a message queue
|
||||||
|
if (to_schedule->state == OSThreadState::BLOCKED_STOPPED) {
|
||||||
|
to_schedule->state = OSThreadState::BLOCKED_PAUSED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
to_schedule->state = OSThreadState::PAUSED;
|
||||||
|
running_thread_queue.push(to_schedule);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_thread_stopping(thread_queue_t& running_thread_queue, const StopThreadAction& action) {
|
void handle_thread_stopping(thread_queue_t& running_thread_queue, const StopThreadAction& action) {
|
||||||
OSThread* to_stop = action.t;
|
OSThread* to_stop = action.t;
|
||||||
debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id);
|
debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id);
|
||||||
|
|
||||||
running_thread_queue.remove(to_stop);
|
running_thread_queue.remove(to_stop);
|
||||||
|
if (running_thread_queue.contains(to_stop)) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_stop->state == OSThreadState::BLOCKED_PAUSED) {
|
||||||
|
to_stop->state = OSThreadState::BLOCKED_STOPPED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
to_stop->state = OSThreadState::STOPPED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_thread_cleanup(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread, const CleanupThreadAction& action) {
|
void handle_thread_cleanup(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread, const CleanupThreadAction& action) {
|
||||||
@ -111,17 +150,76 @@ void handle_thread_reprioritization(thread_queue_t& running_thread_queue, const
|
|||||||
running_thread_queue.push(to_reprioritize);
|
running_thread_queue.push(to_reprioritize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handle_thread_yielded(thread_queue_t& running_thread_queue, const YieldedThreadAction& action) {
|
||||||
|
OSThread* yielded = action.t;
|
||||||
|
|
||||||
|
debug_printf("[Scheduler] Thread %d has yielded\n", yielded->id);
|
||||||
|
// Remove the yielded thread from the thread queue. If it was in the queue then re-add it so that it's placed after any other threads with the same priority.
|
||||||
|
if (running_thread_queue.remove(yielded)) {
|
||||||
|
running_thread_queue.push(yielded);
|
||||||
|
}
|
||||||
|
yielded->state = OSThreadState::PAUSED;
|
||||||
|
debug_printf("[Scheduler] Set thread %d to PAUSED\n", yielded->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_thread_blocked(thread_queue_t& running_thread_queue, const BlockedThreadAction& action) {
|
||||||
|
OSThread* blocked = action.t;
|
||||||
|
|
||||||
|
debug_printf("[Scheduler] Thread %d has been blocked\n", blocked->id);
|
||||||
|
// Remove the thread from the running queue.
|
||||||
|
running_thread_queue.remove(blocked);
|
||||||
|
|
||||||
|
// Update the thread's state accordingly.
|
||||||
|
if (blocked->state == OSThreadState::STOPPED) {
|
||||||
|
blocked->state = OSThreadState::BLOCKED_STOPPED;
|
||||||
|
}
|
||||||
|
else if (blocked->state == OSThreadState::RUNNING) {
|
||||||
|
blocked->state = OSThreadState::BLOCKED_PAUSED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
running_thread_queue.remove(blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_thread_unblocking(thread_queue_t& running_thread_queue, const UnblockThreadAction& action) {
|
||||||
|
OSThread* unblocked = action.t;
|
||||||
|
|
||||||
|
// Do nothing if this thread has already been unblocked.
|
||||||
|
if (unblocked->state != OSThreadState::BLOCKED_STOPPED && unblocked->state != OSThreadState::BLOCKED_PAUSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_printf("[Scheduler] Thread %d has been unblocked\n", unblocked->id);
|
||||||
|
// Update the thread's state accordingly.
|
||||||
|
if (unblocked->state == OSThreadState::BLOCKED_STOPPED) {
|
||||||
|
unblocked->state = OSThreadState::STOPPED;
|
||||||
|
}
|
||||||
|
else if (unblocked->state == OSThreadState::BLOCKED_PAUSED) {
|
||||||
|
// The thread wasn't stopped, so put it back in the running queue now that it's been unblocked.
|
||||||
|
unblocked->state = OSThreadState::PAUSED;
|
||||||
|
running_thread_queue.push(unblocked);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) {
|
void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) {
|
||||||
if (running_thread_queue.size() > 0) {
|
if (running_thread_queue.size() > 0) {
|
||||||
OSThread* new_running_thread = running_thread_queue.top();
|
OSThread* new_running_thread = running_thread_queue.top();
|
||||||
if (cur_running_thread != new_running_thread) {
|
// If the running thread has changed or the running thread is paused, run the running thread
|
||||||
|
if (cur_running_thread != new_running_thread || (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING)) {
|
||||||
if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) {
|
if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) {
|
||||||
debug_printf("[Scheduler] Need to wait for thread %d to pause itself\n", cur_running_thread->id);
|
debug_printf("[Scheduler] Need to wait for thread %d to pause itself\n", cur_running_thread->id);
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority);
|
|
||||||
}
|
}
|
||||||
|
debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority);
|
||||||
Multilibultra::resume_thread_impl(new_running_thread);
|
Multilibultra::resume_thread_impl(new_running_thread);
|
||||||
|
if (cur_running_thread) {
|
||||||
|
cur_running_thread->context->descheduled.store(true);
|
||||||
|
cur_running_thread->context->descheduled.notify_all();
|
||||||
|
}
|
||||||
cur_running_thread = new_running_thread;
|
cur_running_thread = new_running_thread;
|
||||||
} else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) {
|
} else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) {
|
||||||
Multilibultra::resume_thread_impl(cur_running_thread);
|
Multilibultra::resume_thread_impl(cur_running_thread);
|
||||||
@ -139,28 +237,41 @@ void scheduler_func() {
|
|||||||
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::VeryHigh);
|
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::VeryHigh);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ThreadAction action;
|
using namespace std::chrono_literals;
|
||||||
|
ThreadAction action{};
|
||||||
OSThread* old_running_thread = cur_running_thread;
|
OSThread* old_running_thread = cur_running_thread;
|
||||||
|
//scheduler_context.action_queue.wait_dequeue_timed(action, 1ms);
|
||||||
scheduler_context.action_queue.wait_dequeue(action);
|
scheduler_context.action_queue.wait_dequeue(action);
|
||||||
|
|
||||||
std::lock_guard lock{scheduler_context.premption_mutex};
|
if (std::get_if<std::monostate>(&action) == nullptr) {
|
||||||
|
// Determine the action type and act on it
|
||||||
|
if (const auto* notify_action = std::get_if<NotifySchedulerAction>(&action)) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
else if (const auto* stop_action = std::get_if<StopThreadAction>(&action)) {
|
||||||
|
handle_thread_stopping(running_thread_queue, *stop_action);
|
||||||
|
}
|
||||||
|
else if (const auto* cleanup_action = std::get_if<CleanupThreadAction>(&action)) {
|
||||||
|
handle_thread_cleanup(running_thread_queue, cur_running_thread, *cleanup_action);
|
||||||
|
}
|
||||||
|
else if (const auto* schedule_action = std::get_if<ScheduleThreadAction>(&action)) {
|
||||||
|
handle_thread_queueing(running_thread_queue, *schedule_action);
|
||||||
|
}
|
||||||
|
else if (const auto* reprioritize_action = std::get_if<ReprioritizeThreadAction>(&action)) {
|
||||||
|
handle_thread_reprioritization(running_thread_queue, *reprioritize_action);
|
||||||
|
}
|
||||||
|
else if (const auto* yielded_action = std::get_if<YieldedThreadAction>(&action)) {
|
||||||
|
handle_thread_yielded(running_thread_queue, *yielded_action);
|
||||||
|
}
|
||||||
|
else if (const auto* blocked_action = std::get_if<BlockedThreadAction>(&action)) {
|
||||||
|
handle_thread_blocked(running_thread_queue, *blocked_action);
|
||||||
|
}
|
||||||
|
else if (const auto* unblock_action = std::get_if<UnblockThreadAction>(&action)) {
|
||||||
|
handle_thread_unblocking(running_thread_queue, *unblock_action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the action type and act on it
|
running_thread_queue.print();
|
||||||
if (const auto* cleanup_action = std::get_if<NotifySchedulerAction>(&action)) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
else if (const auto* stop_action = std::get_if<StopThreadAction>(&action)) {
|
|
||||||
handle_thread_stopping(running_thread_queue, *stop_action);
|
|
||||||
}
|
|
||||||
else if (const auto* cleanup_action = std::get_if<CleanupThreadAction>(&action)) {
|
|
||||||
handle_thread_cleanup(running_thread_queue, cur_running_thread, *cleanup_action);
|
|
||||||
}
|
|
||||||
else if (const auto* schedule_action = std::get_if<ScheduleThreadAction>(&action)) {
|
|
||||||
handle_thread_queueing(running_thread_queue, *schedule_action);
|
|
||||||
}
|
|
||||||
else if (const auto* reprioritize_action = std::get_if<ReprioritizeThreadAction>(&action)) {
|
|
||||||
handle_thread_reprioritization(running_thread_queue, *reprioritize_action);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine which thread to run, stopping the current running thread if necessary
|
// Determine which thread to run, stopping the current running thread if necessary
|
||||||
swap_running_thread(running_thread_queue, cur_running_thread);
|
swap_running_thread(running_thread_queue, cur_running_thread);
|
||||||
@ -180,78 +291,90 @@ extern "C" void do_yield() {
|
|||||||
namespace Multilibultra {
|
namespace Multilibultra {
|
||||||
|
|
||||||
void init_scheduler() {
|
void init_scheduler() {
|
||||||
scheduler_context.can_preempt = true;
|
|
||||||
std::thread scheduler_thread{scheduler_func};
|
std::thread scheduler_thread{scheduler_func};
|
||||||
scheduler_thread.detach();
|
scheduler_thread.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
void schedule_running_thread(OSThread *t) {
|
void schedule_running_thread(OSThread *t) {
|
||||||
debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id);
|
debug_printf("[Thread] Queuing Thread %d to be scheduled\n", t->id);
|
||||||
scheduler_context.action_queue.enqueue(ScheduleThreadAction{t});
|
scheduler_context.action_queue.enqueue(ScheduleThreadAction{t});
|
||||||
}
|
}
|
||||||
|
|
||||||
void swap_to_thread(RDRAM_ARG OSThread *to) {
|
void swap_to_thread(RDRAM_ARG OSThread *to) {
|
||||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||||
debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id);
|
debug_printf("[Thread] Scheduling swap from thread %d to %d\n", self->id, to->id);
|
||||||
|
|
||||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
// Tell the scheduler that the swapped-to thread is ready to run and that this thread is yielding.
|
||||||
scheduler_context.action_queue.enqueue(ScheduleThreadAction{to});
|
schedule_running_thread(to);
|
||||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
yield_self(PASS_RDRAM1);
|
||||||
|
|
||||||
|
// Wait for the scheduler to resume this thread.
|
||||||
|
wait_for_resumed(PASS_RDRAM1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reprioritize_thread(OSThread *t, OSPri pri) {
|
void reprioritize_thread(OSThread *t, OSPri pri) {
|
||||||
debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri);
|
debug_printf("[Thread] Adjusting Thread %d priority to %d\n", t->id, pri);
|
||||||
|
|
||||||
scheduler_context.action_queue.enqueue(ReprioritizeThreadAction{t, pri});
|
scheduler_context.action_queue.enqueue(ReprioritizeThreadAction{t, pri});
|
||||||
}
|
}
|
||||||
|
|
||||||
void pause_self(RDRAM_ARG1) {
|
void stop_thread(OSThread *t) {
|
||||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
debug_printf("[Thread] Queueing stopping of thread %d\n", t->id);
|
||||||
debug_printf("[Scheduler] Thread %d pausing itself\n", self->id);
|
|
||||||
|
|
||||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
scheduler_context.action_queue.enqueue(StopThreadAction{t});
|
||||||
scheduler_context.action_queue.enqueue(StopThreadAction{self});
|
}
|
||||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
|
||||||
|
void Multilibultra::yield_self(RDRAM_ARG1) {
|
||||||
|
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||||
|
debug_printf("[Thread] Thread %d yielding itself\n", self->id);
|
||||||
|
|
||||||
|
scheduler_context.action_queue.enqueue(YieldedThreadAction{ self });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Multilibultra::block_self(RDRAM_ARG1) {
|
||||||
|
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||||
|
debug_printf("[Thread] Thread %d has been blocked\n", self->id);
|
||||||
|
|
||||||
|
scheduler_context.action_queue.enqueue(BlockedThreadAction{ self });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Multilibultra::unblock_thread(OSThread *t) {
|
||||||
|
debug_printf("[Thread] Unblocking thread %d\n", t->id);
|
||||||
|
|
||||||
|
scheduler_context.action_queue.enqueue(UnblockThreadAction{ t });
|
||||||
|
}
|
||||||
|
|
||||||
|
void halt_self(RDRAM_ARG1) {
|
||||||
|
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||||
|
debug_printf("[Thread] Thread %d pausing itself\n", self->id);
|
||||||
|
|
||||||
|
stop_thread(self);
|
||||||
|
yield_self(PASS_RDRAM1);
|
||||||
|
wait_for_resumed(PASS_RDRAM1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup_thread(OSThread *t) {
|
void cleanup_thread(OSThread *t) {
|
||||||
scheduler_context.action_queue.enqueue(CleanupThreadAction{t});
|
scheduler_context.action_queue.enqueue(CleanupThreadAction{t});
|
||||||
}
|
}
|
||||||
|
|
||||||
void disable_preemption() {
|
|
||||||
scheduler_context.premption_mutex.lock();
|
|
||||||
if (Multilibultra::is_game_thread()) {
|
|
||||||
scheduler_context.can_preempt = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void enable_preemption() {
|
|
||||||
if (Multilibultra::is_game_thread()) {
|
|
||||||
scheduler_context.can_preempt = true;
|
|
||||||
}
|
|
||||||
#pragma warning(push)
|
|
||||||
#pragma warning( disable : 26110)
|
|
||||||
scheduler_context.premption_mutex.unlock();
|
|
||||||
#pragma warning( pop )
|
|
||||||
}
|
|
||||||
|
|
||||||
// lock's constructor is called first, so can_preempt is set after locking
|
|
||||||
preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} {
|
|
||||||
scheduler_context.can_preempt = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lock's destructor is called last, so can_preempt is set before unlocking
|
|
||||||
preemption_guard::~preemption_guard() {
|
|
||||||
scheduler_context.can_preempt = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void notify_scheduler() {
|
void notify_scheduler() {
|
||||||
scheduler_context.action_queue.enqueue(NotifySchedulerAction{});
|
scheduler_context.action_queue.enqueue(NotifySchedulerAction{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resume_thread_impl(OSThread* t) {
|
||||||
|
if (t->state == OSThreadState::PREEMPTED) {
|
||||||
|
// Nothing to do here
|
||||||
|
}
|
||||||
|
t->state = OSThreadState::RUNNING;
|
||||||
|
debug_printf("[Scheduler] Set thread %d to RUNNING\n", t->id);
|
||||||
|
t->context->scheduled.store(true);
|
||||||
|
t->context->scheduled.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void pause_self(uint8_t* rdram) {
|
extern "C" void pause_self(uint8_t* rdram) {
|
||||||
Multilibultra::pause_self(rdram);
|
Multilibultra::halt_self(rdram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,8 @@
|
|||||||
#include "multilibultra.hpp"
|
#include "multilibultra.hpp"
|
||||||
|
|
||||||
// Native APIs only used to set thread names for easier debugging
|
// Native APIs only used to set thread names for easier debugging
|
||||||
#if defined(_WIN32)
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#elif defined(__linux__)
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern "C" void bootproc();
|
extern "C" void bootproc();
|
||||||
@ -127,7 +125,6 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry
|
|||||||
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::High);
|
Multilibultra::set_native_thread_priority(Multilibultra::ThreadPriority::High);
|
||||||
|
|
||||||
// Set initialized to false to indicate that this thread can be started.
|
// Set initialized to false to indicate that this thread can be started.
|
||||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
|
||||||
self->context->initialized.store(true);
|
self->context->initialized.store(true);
|
||||||
self->context->initialized.notify_all();
|
self->context->initialized.notify_all();
|
||||||
|
|
||||||
@ -153,7 +150,7 @@ extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {
|
|||||||
OSThread* t = TO_PTR(OSThread, t_);
|
OSThread* t = TO_PTR(OSThread, t_);
|
||||||
debug_printf("[os] Start Thread %d\n", t->id);
|
debug_printf("[os] Start Thread %d\n", t->id);
|
||||||
|
|
||||||
// Wait until the thread is initialized to indicate that it's action_queued to be started.
|
// Wait until the thread is initialized to indicate that it's queued to be started.
|
||||||
t->context->initialized.wait(false);
|
t->context->initialized.wait(false);
|
||||||
|
|
||||||
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
||||||
@ -178,20 +175,33 @@ extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_f
|
|||||||
t->next = NULLPTR;
|
t->next = NULLPTR;
|
||||||
t->priority = pri;
|
t->priority = pri;
|
||||||
t->id = id;
|
t->id = id;
|
||||||
t->state = OSThreadState::PAUSED;
|
t->state = OSThreadState::STOPPED;
|
||||||
t->sp = sp - 0x10; // Set up the first stack frame
|
t->sp = sp - 0x10; // Set up the first stack frame
|
||||||
t->destroyed = false;
|
t->destroyed = false;
|
||||||
|
|
||||||
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
||||||
t->context = new UltraThreadContext{};
|
t->context = new UltraThreadContext{};
|
||||||
t->context->initialized.store(false);
|
t->context->initialized.store(false);
|
||||||
t->context->running.store(false);
|
t->context->scheduled.store(false);
|
||||||
|
t->context->descheduled.store(true);
|
||||||
|
|
||||||
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
|
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
|
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||||
assert(false);
|
// If null is passed in as the thread then the calling thread is stopping itself.
|
||||||
|
if (t_ == NULLPTR) {
|
||||||
|
t_ = Multilibultra::this_thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the thread in question from the scheduler so it doesn't get scheduled again.
|
||||||
|
OSThread* t = TO_PTR(OSThread, t_);
|
||||||
|
Multilibultra::stop_thread(t);
|
||||||
|
|
||||||
|
// If a thread is stopping itself, tell the scheduler that it has yielded.
|
||||||
|
if (t_ == Multilibultra::this_thread()) {
|
||||||
|
Multilibultra::yield_self(PASS_RDRAM1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||||
@ -207,6 +217,12 @@ extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO make the thread queue stable to ensure correct yielding behavior
|
||||||
|
extern "C" void osYieldThread(RDRAM_ARG1) {
|
||||||
|
Multilibultra::yield_self(PASS_RDRAM1);
|
||||||
|
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
|
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
|
||||||
if (t == NULLPTR) {
|
if (t == NULLPTR) {
|
||||||
t = thread_self;
|
t = thread_self;
|
||||||
@ -214,13 +230,12 @@ extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
|
|||||||
bool pause_self = false;
|
bool pause_self = false;
|
||||||
if (pri > TO_PTR(OSThread, thread_self)->priority) {
|
if (pri > TO_PTR(OSThread, thread_self)->priority) {
|
||||||
pause_self = true;
|
pause_self = true;
|
||||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
|
||||||
} else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
|
} else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
|
||||||
pause_self = true;
|
pause_self = true;
|
||||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
|
||||||
}
|
}
|
||||||
Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri);
|
Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri);
|
||||||
if (pause_self) {
|
if (pause_self) {
|
||||||
|
Multilibultra::yield_self(PASS_RDRAM1);
|
||||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,15 +254,6 @@ extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) {
|
|||||||
return TO_PTR(OSThread, t)->id;
|
return TO_PTR(OSThread, t)->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO yield thread, need a stable priority queue in the scheduler
|
|
||||||
|
|
||||||
void Multilibultra::set_self_paused(RDRAM_ARG1) {
|
|
||||||
debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id);
|
|
||||||
TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED;
|
|
||||||
TO_PTR(OSThread, thread_self)->context->running.store(false);
|
|
||||||
TO_PTR(OSThread, thread_self)->context->running.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void check_destroyed(OSThread* t) {
|
void check_destroyed(OSThread* t) {
|
||||||
if (t->destroyed) {
|
if (t->destroyed) {
|
||||||
throw thread_terminated{};
|
throw thread_terminated{};
|
||||||
@ -256,25 +262,13 @@ void check_destroyed(OSThread* t) {
|
|||||||
|
|
||||||
void Multilibultra::wait_for_resumed(RDRAM_ARG1) {
|
void Multilibultra::wait_for_resumed(RDRAM_ARG1) {
|
||||||
check_destroyed(TO_PTR(OSThread, thread_self));
|
check_destroyed(TO_PTR(OSThread, thread_self));
|
||||||
TO_PTR(OSThread, thread_self)->context->running.wait(false);
|
//TO_PTR(OSThread, thread_self)->context->descheduled.wait(false);
|
||||||
|
//TO_PTR(OSThread, thread_self)->context->descheduled.store(false);
|
||||||
|
TO_PTR(OSThread, thread_self)->context->scheduled.wait(false);
|
||||||
|
TO_PTR(OSThread, thread_self)->context->scheduled.store(false);
|
||||||
check_destroyed(TO_PTR(OSThread, thread_self));
|
check_destroyed(TO_PTR(OSThread, thread_self));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Multilibultra::pause_thread_impl(OSThread* t) {
|
|
||||||
t->state = OSThreadState::PREEMPTED;
|
|
||||||
t->context->running.store(false);
|
|
||||||
t->context->running.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Multilibultra::resume_thread_impl(OSThread *t) {
|
|
||||||
if (t->state == OSThreadState::PREEMPTED) {
|
|
||||||
// Nothing to do here
|
|
||||||
}
|
|
||||||
t->state = OSThreadState::RUNNING;
|
|
||||||
t->context->running.store(true);
|
|
||||||
t->context->running.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
PTR(OSThread) Multilibultra::this_thread() {
|
PTR(OSThread) Multilibultra::this_thread() {
|
||||||
return thread_self;
|
return thread_self;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "ultra64.h"
|
#include "ultra64.h"
|
||||||
#include "multilibultra.hpp"
|
#include "multilibultra.hpp"
|
||||||
#include "recomp.h"
|
|
||||||
|
|
||||||
// Start time for the program
|
// Start time for the program
|
||||||
static std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
|
static std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
|
||||||
@ -24,7 +23,7 @@ struct OSTimer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AddTimerAction {
|
struct AddTimerAction {
|
||||||
PTR(OSTask) timer;
|
PTR(OSTimer) timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RemoveTimerAction {
|
struct RemoveTimerAction {
|
||||||
|
@ -78,6 +78,9 @@ typedef struct UltraThreadContext UltraThreadContext;
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
RUNNING,
|
RUNNING,
|
||||||
PAUSED,
|
PAUSED,
|
||||||
|
STOPPED,
|
||||||
|
BLOCKED_PAUSED,
|
||||||
|
BLOCKED_STOPPED,
|
||||||
PREEMPTED
|
PREEMPTED
|
||||||
} OSThreadState;
|
} OSThreadState;
|
||||||
|
|
||||||
@ -101,9 +104,11 @@ typedef PTR(void) OSMesg;
|
|||||||
typedef struct OSMesgQueue {
|
typedef struct OSMesgQueue {
|
||||||
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
|
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
|
||||||
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
|
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
|
||||||
s32 validCount; /* Number of messages in the queue */
|
s32 validCount; /* Number of messages in the queue */
|
||||||
s32 first; /* Index of the first message in the ring buffer */
|
s32 first; /* Index of the first message in the ring buffer */
|
||||||
s32 msgCount; /* Size of message buffer */
|
uint8_t lock; /* Lock flag used to implement a spinlock */
|
||||||
|
uint8_t pad; /* Explicit padding (would be compiler-inserted otherwise) */
|
||||||
|
s16 msgCount; /* Size of message buffer (s32 in the original libultra, but s16 here to make room for the lock flag) */
|
||||||
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
|
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
|
||||||
} OSMesgQueue;
|
} OSMesgQueue;
|
||||||
|
|
||||||
@ -218,6 +223,7 @@ void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry
|
|||||||
void osStartThread(RDRAM_ARG PTR(OSThread) t);
|
void osStartThread(RDRAM_ARG PTR(OSThread) t);
|
||||||
void osStopThread(RDRAM_ARG PTR(OSThread) t);
|
void osStopThread(RDRAM_ARG PTR(OSThread) t);
|
||||||
void osDestroyThread(RDRAM_ARG PTR(OSThread) t);
|
void osDestroyThread(RDRAM_ARG PTR(OSThread) t);
|
||||||
|
void osYieldThread(RDRAM_ARG1);
|
||||||
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
|
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
|
||||||
OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread);
|
OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread);
|
||||||
OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t);
|
OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include "ultra64.h"
|
#include "ultra64.h"
|
||||||
#include "multilibultra.hpp"
|
#include "multilibultra.hpp"
|
||||||
|
|
||||||
void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom, void* window_handle) {
|
void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom, Multilibultra::WindowHandle window_handle) {
|
||||||
Multilibultra::set_main_thread();
|
Multilibultra::set_main_thread();
|
||||||
Multilibultra::init_events(rdram, rom, window_handle);
|
Multilibultra::init_events(rdram, rom, window_handle);
|
||||||
Multilibultra::init_timers(rdram);
|
Multilibultra::init_timers(rdram);
|
||||||
|
43
src/dp.cpp
43
src/dp.cpp
@ -1,5 +1,44 @@
|
|||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
|
|
||||||
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
enum class RDPStatusBit {
|
||||||
;
|
XbusDmem = 0,
|
||||||
|
Freeze = 1,
|
||||||
|
Flush = 2,
|
||||||
|
CommandBusy = 6,
|
||||||
|
BufferReady = 7,
|
||||||
|
DmaBusy = 8,
|
||||||
|
EndValid = 9,
|
||||||
|
StartValid = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr void update_bit(uint32_t& state, uint32_t flags, RDPStatusBit bit) {
|
||||||
|
int set_bit_pos = (int)bit * 2 + 0;
|
||||||
|
int reset_bit_pos = (int)bit * 2 + 1;
|
||||||
|
bool set = (flags & (1U << set_bit_pos)) != 0;
|
||||||
|
bool reset = (flags & (1U << reset_bit_pos)) != 0;
|
||||||
|
|
||||||
|
if (set ^ reset) {
|
||||||
|
if (set) {
|
||||||
|
state |= (1U << (int)bit);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state &= ~(1U << (int)bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rdp_state = 1 << (int)RDPStatusBit::BufferReady;
|
||||||
|
|
||||||
|
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void osDpGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
ctx->r2 = rdp_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void osDpSetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
update_bit(rdp_state, ctx->r4, RDPStatusBit::XbusDmem);
|
||||||
|
update_bit(rdp_state, ctx->r4, RDPStatusBit::Freeze);
|
||||||
|
update_bit(rdp_state, ctx->r4, RDPStatusBit::Flush);
|
||||||
}
|
}
|
||||||
|
38
src/eep.cpp
38
src/eep.cpp
@ -1,21 +1,49 @@
|
|||||||
#include "recomp.h"
|
#include "recomp.h"
|
||||||
|
#include "../portultra/ultra64.h"
|
||||||
|
|
||||||
|
void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count);
|
||||||
|
void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count);
|
||||||
|
|
||||||
|
constexpr int eeprom_block_size = 8;
|
||||||
|
constexpr int eep4_size = 4096;
|
||||||
|
constexpr int eep4_block_count = eep4_size / eeprom_block_size;
|
||||||
|
constexpr int eep16_size = 16384;
|
||||||
|
constexpr int eep16_block_count = eep16_size / eeprom_block_size;
|
||||||
|
|
||||||
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
ctx->r2 = 0x02; // EEP16K
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
uint8_t eep_address = ctx->r5;
|
||||||
|
gpr buffer = ctx->r6;
|
||||||
|
int32_t nbytes = ctx->r7;
|
||||||
|
|
||||||
|
assert(!(nbytes & 7));
|
||||||
|
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||||
|
|
||||||
|
save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||||
|
|
||||||
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
uint8_t eep_address = ctx->r5;
|
||||||
|
gpr buffer = ctx->r6;
|
||||||
|
int32_t nbytes = ctx->r7;
|
||||||
|
|
||||||
|
assert(!(nbytes & 7));
|
||||||
|
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||||
|
|
||||||
|
save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||||
|
|
||||||
|
ctx->r2 = 0;
|
||||||
}
|
}
|
||||||
|
366
src/main/main.cpp
Normal file
366
src/main/main.cpp
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <cassert>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "../../portultra/ultra64.h"
|
||||||
|
#include "../../portultra/multilibultra.hpp"
|
||||||
|
#define SDL_MAIN_HANDLED
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include "SDL.h"
|
||||||
|
#else
|
||||||
|
#include "SDL2/SDL.h"
|
||||||
|
#include "SDL2/SDL_syswm.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define NOMINMAX
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
#include "SDL_syswm.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" void init();
|
||||||
|
/*extern "C"*/ void start(Multilibultra::WindowHandle window_handle, const Multilibultra::audio_callbacks_t* audio_callbacks, const Multilibultra::input_callbacks_t* input_callbacks);
|
||||||
|
|
||||||
|
template<typename... Ts>
|
||||||
|
void exit_error(const char* str, Ts ...args) {
|
||||||
|
// TODO pop up an error
|
||||||
|
((void)fprintf(stderr, str, args), ...);
|
||||||
|
assert(false);
|
||||||
|
std::quick_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::pair<SDL_Scancode, int>> keyboard_button_map{
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_LEFT, 0x0002 }, // c left
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_RIGHT, 0x0001 }, // c right
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_UP, 0x0008 }, // c up
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_DOWN, 0x0004 }, // c down
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_SPACE, 0x8000 }, // a
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_Q, 0x2000 }, // z
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_E, 0x0020 }, // l
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_R, 0x0010 }, // r
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_J, 0x0200 }, // dpad left
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_L, 0x0100 }, // dpad right
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_I, 0x0800 }, // dpad up
|
||||||
|
{ SDL_Scancode::SDL_SCANCODE_K, 0x0400 }, // dpad down
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameControllerAxisMapping {
|
||||||
|
SDL_GameControllerAxis axis;
|
||||||
|
int threshold; // Positive or negative to indicate direction
|
||||||
|
uint16_t output_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int controller_default_threshold = 20000;
|
||||||
|
|
||||||
|
std::vector<GameControllerAxisMapping> controller_axis_map{
|
||||||
|
{ SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX, -controller_default_threshold, 0x0002 }, // c left
|
||||||
|
{ SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX, controller_default_threshold, 0x0001 }, // c right
|
||||||
|
{ SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY, -controller_default_threshold, 0x0008 }, // c up
|
||||||
|
{ SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY, controller_default_threshold, 0x0004 }, // c down
|
||||||
|
{ SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERLEFT, 10000, 0x2000 }, // z
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_RIGHT, 0x0001 }, // c right
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_UP, 0x0008 }, // c up
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_DOWN, 0x0004 }, // c down
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_SPACE, 0x8000 }, // a
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_Q, 0x2000 }, // z
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_E, 0x0020 }, // l
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_R, 0x0010 }, // r
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_J, 0x0200 }, // dpad left
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_L, 0x0100 }, // dpad right
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_I, 0x0800 }, // dpad up
|
||||||
|
//{ SDL_Scancode::SDL_SCANCODE_K, 0x0400 }, // dpad down
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameControllerButtonMapping {
|
||||||
|
SDL_GameControllerButton button;
|
||||||
|
uint16_t output_mask;
|
||||||
|
};
|
||||||
|
std::vector<GameControllerButtonMapping> controller_button_map{
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_START, 0x1000 }, // start
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A, 0x8000 }, // a
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_B, 0x4000 }, // b
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_X, 0x4000 }, // b
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 0x0020 }, // l
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 0x0010 }, // r
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT, 0x0200 }, // dpad left
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 0x0100 }, // dpad right
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP, 0x0800 }, // dpad up
|
||||||
|
{ SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN, 0x0400 }, // dpad down
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SDL_JoystickID> controllers{};
|
||||||
|
|
||||||
|
int sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||||
|
switch (event->type) {
|
||||||
|
//case SDL_EventType::SDL_KEYUP:
|
||||||
|
//case SDL_EventType::SDL_KEYDOWN:
|
||||||
|
// {
|
||||||
|
// const Uint8* key_states = SDL_GetKeyboardState(nullptr);
|
||||||
|
// int new_button = 0;
|
||||||
|
|
||||||
|
// for (const auto& mapping : keyboard_button_map) {
|
||||||
|
// if (key_states[mapping.first]) {
|
||||||
|
// new_button |= mapping.second;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// button = new_button;
|
||||||
|
|
||||||
|
// stick_x = (100.0f / 100.0f) * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]);
|
||||||
|
// stick_y = (100.0f / 100.0f) * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]);
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
case SDL_EventType::SDL_CONTROLLERDEVICEADDED:
|
||||||
|
{
|
||||||
|
SDL_ControllerDeviceEvent* controller_event = (SDL_ControllerDeviceEvent*)event;
|
||||||
|
SDL_GameController* controller = SDL_GameControllerOpen(controller_event->which);
|
||||||
|
printf("Controller added: %d\n", controller_event->which);
|
||||||
|
if (controller != nullptr) {
|
||||||
|
printf(" Instance ID: %d\n", SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
|
||||||
|
controllers.push_back(SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
{
|
||||||
|
SDL_ControllerDeviceEvent* controller_event = (SDL_ControllerDeviceEvent*)event;
|
||||||
|
printf("Controller removed: %d\n", controller_event->which);
|
||||||
|
std::remove(controllers.begin(), controllers.end(), controller_event->which);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_EventType::SDL_QUIT:
|
||||||
|
std::quick_exit(EXIT_SUCCESS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Multilibultra::gfx_callbacks_t::gfx_data_t create_gfx() {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) {
|
||||||
|
exit_error("Failed to initialize SDL2: %s\n", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Multilibultra::WindowHandle create_window(Multilibultra::gfx_callbacks_t::gfx_data_t) {
|
||||||
|
SDL_Window* window = SDL_CreateWindow("Majora's Mask", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE);
|
||||||
|
|
||||||
|
if (window == nullptr) {
|
||||||
|
exit_error("Failed to create window: %s\n", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SysWMinfo wmInfo;
|
||||||
|
SDL_VERSION(&wmInfo.version);
|
||||||
|
SDL_GetWindowWMInfo(window, &wmInfo);
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return wmInfo.info.win.window;
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
static_assert(false && "Unimplemented");
|
||||||
|
#elif defined(__linux__)
|
||||||
|
return Multilibultra::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
|
||||||
|
#else
|
||||||
|
static_assert(false && "Unimplemented");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_gfx(void*) {
|
||||||
|
// Handle events
|
||||||
|
constexpr int max_events_per_frame = 16;
|
||||||
|
SDL_Event cur_event;
|
||||||
|
int i = 0;
|
||||||
|
while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) {
|
||||||
|
sdl_event_filter(nullptr, &cur_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_input(uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||||
|
uint16_t cur_buttons = 0;
|
||||||
|
float cur_x = 0.0f;
|
||||||
|
float cur_y = 0.0f;
|
||||||
|
|
||||||
|
const Uint8* key_states = SDL_GetKeyboardState(nullptr);
|
||||||
|
int new_button = 0;
|
||||||
|
|
||||||
|
for (const auto& mapping : keyboard_button_map) {
|
||||||
|
if (key_states[mapping.first]) {
|
||||||
|
cur_buttons |= mapping.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_x += (100.0f / 100.0f) * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]);
|
||||||
|
cur_y += (100.0f / 100.0f) * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]);
|
||||||
|
|
||||||
|
for (SDL_JoystickID controller_id : controllers) {
|
||||||
|
SDL_GameController* controller = SDL_GameControllerFromInstanceID(controller_id);
|
||||||
|
if (controller != nullptr) {
|
||||||
|
cur_x += SDL_GameControllerGetAxis(controller, SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX) * (1/32768.0f);
|
||||||
|
cur_y -= SDL_GameControllerGetAxis(controller, SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY) * (1/32768.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& mapping : controller_axis_map) {
|
||||||
|
int input_value = SDL_GameControllerGetAxis(controller, mapping.axis);
|
||||||
|
if (mapping.threshold > 0) {
|
||||||
|
if (input_value > mapping.threshold) {
|
||||||
|
cur_buttons |= mapping.output_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (input_value < mapping.threshold) {
|
||||||
|
cur_buttons |= mapping.output_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& mapping : controller_button_map) {
|
||||||
|
int input_value = SDL_GameControllerGetButton(controller, mapping.button);
|
||||||
|
if (input_value) {
|
||||||
|
cur_buttons |= mapping.output_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*buttons_out = cur_buttons;
|
||||||
|
cur_x = std::clamp(cur_x, -1.0f, 1.0f);
|
||||||
|
cur_y = std::clamp(cur_y, -1.0f, 1.0f);
|
||||||
|
*x_out = cur_x;
|
||||||
|
*y_out = cur_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_AudioDeviceID audio_device = 0;
|
||||||
|
static uint32_t sample_rate = 48000;
|
||||||
|
|
||||||
|
void queue_samples(int16_t* audio_data, size_t sample_count) {
|
||||||
|
// Buffer for holding the output of swapping the audio channels. This is reused across
|
||||||
|
// calls to reduce runtime allocations.
|
||||||
|
static std::vector<float> swap_buffer;
|
||||||
|
|
||||||
|
// Make sure the swap buffer is large enough to hold all the incoming audio data.
|
||||||
|
if (sample_count > swap_buffer.size()) {
|
||||||
|
swap_buffer.resize(sample_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the audio from 16-bit values to floats and swap the audio channels into the
|
||||||
|
// swap buffer to correct for the address xor caused by endianness handling.
|
||||||
|
for (size_t i = 0; i < sample_count; i += 2) {
|
||||||
|
swap_buffer[i + 0] = audio_data[i + 1] * (0.5f / 32768.0f);
|
||||||
|
swap_buffer[i + 1] = audio_data[i + 0] * (0.5f / 32768.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the swapped audio data.
|
||||||
|
SDL_QueueAudio(audio_device, swap_buffer.data(), sample_count * sizeof(swap_buffer[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int channel_count = 2;
|
||||||
|
constexpr int bytes_per_frame = channel_count * sizeof(float);
|
||||||
|
|
||||||
|
size_t get_frames_remaining() {
|
||||||
|
constexpr float buffer_offset_frames = 1.0f;
|
||||||
|
// Get the number of remaining buffered audio bytes.
|
||||||
|
uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device);
|
||||||
|
|
||||||
|
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
|
||||||
|
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
|
||||||
|
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||||
|
// to generate.
|
||||||
|
uint32_t frames_per_vi = (sample_rate / 60);
|
||||||
|
if (buffered_byte_count > (buffer_offset_frames * bytes_per_frame * frames_per_vi)) {
|
||||||
|
buffered_byte_count -= (buffer_offset_frames * bytes_per_frame * frames_per_vi);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffered_byte_count = 0;
|
||||||
|
}
|
||||||
|
// Convert from byte count to sample count.
|
||||||
|
return buffered_byte_count / bytes_per_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_frequency(uint32_t freq) {
|
||||||
|
if (audio_device != 0) {
|
||||||
|
SDL_CloseAudioDevice(audio_device);
|
||||||
|
}
|
||||||
|
SDL_AudioSpec spec_desired{
|
||||||
|
.freq = (int)freq,
|
||||||
|
.format = AUDIO_F32,
|
||||||
|
.channels = channel_count,
|
||||||
|
.silence = 0, // calculated
|
||||||
|
.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
|
||||||
|
.padding = 0, // unused
|
||||||
|
.size = 0, // calculated
|
||||||
|
.callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping
|
||||||
|
.userdata = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0);
|
||||||
|
if (audio_device == 0) {
|
||||||
|
exit_error("SDL error opening audio device: %s\n", SDL_GetError());
|
||||||
|
}
|
||||||
|
SDL_PauseAudioDevice(audio_device, 0);
|
||||||
|
sample_rate = freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Set up console output to accept UTF-8 on windows
|
||||||
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
|
|
||||||
|
// Change to a font that supports Japanese characters
|
||||||
|
CONSOLE_FONT_INFOEX cfi;
|
||||||
|
cfi.cbSize = sizeof cfi;
|
||||||
|
cfi.nFont = 0;
|
||||||
|
cfi.dwFontSize.X = 0;
|
||||||
|
cfi.dwFontSize.Y = 16;
|
||||||
|
cfi.FontFamily = FF_DONTCARE;
|
||||||
|
cfi.FontWeight = FW_NORMAL;
|
||||||
|
wcscpy_s(cfi.FaceName, L"NSimSun");
|
||||||
|
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
|
||||||
|
#else
|
||||||
|
std::setlocale(LC_ALL, "en_US.UTF-8");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initialize SDL audio.
|
||||||
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
// Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
|
||||||
|
set_frequency(sample_rate);
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
Multilibultra::gfx_callbacks_t gfx_callbacks{
|
||||||
|
.create_gfx = create_gfx,
|
||||||
|
.create_window = create_window,
|
||||||
|
.update_gfx = update_gfx,
|
||||||
|
};
|
||||||
|
|
||||||
|
Multilibultra::audio_callbacks_t audio_callbacks{
|
||||||
|
.queue_samples = queue_samples,
|
||||||
|
.get_frames_remaining = get_frames_remaining,
|
||||||
|
.set_frequency = set_frequency,
|
||||||
|
};
|
||||||
|
|
||||||
|
Multilibultra::input_callbacks_t input_callbacks{
|
||||||
|
.get_input = get_input,
|
||||||
|
};
|
||||||
|
|
||||||
|
//create_gfx();
|
||||||
|
//void* window_handle = create_window(nullptr);
|
||||||
|
|
||||||
|
Multilibultra::set_gfx_callbacks(&gfx_callbacks);
|
||||||
|
start(Multilibultra::WindowHandle{}, &audio_callbacks, &input_callbacks);
|
||||||
|
|
||||||
|
// Do nothing forever
|
||||||
|
while (1) {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
std::this_thread::sleep_for(10ms);
|
||||||
|
//update_gfx(nullptr);
|
||||||
|
//std::this_thread::sleep_for(1ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
@ -59,6 +59,42 @@ extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||||
|
|
||||||
|
extern "C" void unload_overlay_by_id(uint32_t id) {
|
||||||
|
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||||
|
const SectionTableEntry& section = section_table[section_table_index];
|
||||||
|
|
||||||
|
auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; });
|
||||||
|
|
||||||
|
if (find_it != loaded_sections.end()) {
|
||||||
|
// Determine where each function was loaded to and remove that entry from the function map
|
||||||
|
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||||
|
const auto& func = section.funcs[func_index];
|
||||||
|
uint32_t func_address = func.offset + find_it->loaded_ram_addr;
|
||||||
|
func_map.erase(func_address);
|
||||||
|
}
|
||||||
|
// Reset the section's address in the address table
|
||||||
|
section_addresses[section.index] = section.ram_addr;
|
||||||
|
// Remove the section from the loaded section map
|
||||||
|
loaded_sections.erase(find_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) {
|
||||||
|
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||||
|
const SectionTableEntry& section = section_table[section_table_index];
|
||||||
|
int32_t prev_address = section_addresses[section.index];
|
||||||
|
if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) {
|
||||||
|
load_overlay(section_table_index, ram_addr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t new_address = prev_address + ram_addr;
|
||||||
|
unload_overlay_by_id(id);
|
||||||
|
load_overlay(section_table_index, new_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
||||||
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
|
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
|
||||||
const auto& section = section_table[it->section_table_index];
|
const auto& section = section_table[it->section_table_index];
|
||||||
@ -72,6 +108,7 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
|||||||
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
|
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
|
||||||
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
|
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
|
||||||
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
|
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
|
||||||
|
assert(false);
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
// Determine where each function was loaded to and remove that entry from the function map
|
// Determine where each function was loaded to and remove that entry from the function map
|
||||||
@ -81,7 +118,7 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
|||||||
func_map.erase(func_address);
|
func_map.erase(func_address);
|
||||||
}
|
}
|
||||||
// Reset the section's address in the address table
|
// Reset the section's address in the address table
|
||||||
section_addresses[section.index] = 0;
|
section_addresses[section.index] = section.ram_addr;
|
||||||
// Remove the section from the loaded section map
|
// Remove the section from the loaded section map
|
||||||
it = loaded_sections.erase(it);
|
it = loaded_sections.erase(it);
|
||||||
// Skip incrementing the iterator
|
// Skip incrementing the iterator
|
||||||
@ -108,6 +145,7 @@ extern "C" recomp_func_t * get_function(int32_t addr) {
|
|||||||
auto func_find = func_map.find(addr);
|
auto func_find = func_map.find(addr);
|
||||||
if (func_find == func_map.end()) {
|
if (func_find == func_map.end()) {
|
||||||
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
||||||
|
assert(false);
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
return func_find->second;
|
return func_find->second;
|
||||||
|
@ -138,7 +138,7 @@ extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) {
|
|||||||
uint32_t mb = ctx->r4;
|
uint32_t mb = ctx->r4;
|
||||||
uint32_t pri = ctx->r5;
|
uint32_t pri = ctx->r5;
|
||||||
uint32_t direction = ctx->r6;
|
uint32_t direction = ctx->r6;
|
||||||
uint32_t devAddr = ctx->r7;
|
uint32_t devAddr = ctx->r7 | rom_base;
|
||||||
gpr dramAddr = MEM_W(0x10, ctx->r29);
|
gpr dramAddr = MEM_W(0x10, ctx->r29);
|
||||||
uint32_t size = MEM_W(0x14, ctx->r29);
|
uint32_t size = MEM_W(0x14, ctx->r29);
|
||||||
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
|
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
|
||||||
|
@ -28,6 +28,10 @@ extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
|||||||
osDestroyThread(rdram, (int32_t)ctx->r4);
|
osDestroyThread(rdram, (int32_t)ctx->r4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void osYieldThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
|
osYieldThread(rdram);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
|
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
|
||||||
}
|
}
|
||||||
@ -85,7 +89,7 @@ extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
|
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r2);
|
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r4);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||||
|
114
src/recomp.cpp
114
src/recomp.cpp
@ -1,4 +1,4 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@ -41,13 +41,58 @@ extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
|||||||
ctx->r2 = 8 * 1024 * 1024;
|
ctx->r2 = 8 * 1024 * 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class StatusReg {
|
||||||
|
FR = 0x04000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" void cop0_status_write(recomp_context* ctx, gpr value) {
|
||||||
|
uint32_t old_sr = ctx->status_reg;
|
||||||
|
uint32_t new_sr = (uint32_t)value;
|
||||||
|
uint32_t changed = old_sr ^ new_sr;
|
||||||
|
|
||||||
|
// Check if the FR bit changed
|
||||||
|
if (changed & (uint32_t)StatusReg::FR) {
|
||||||
|
// Check if the FR bit was set
|
||||||
|
if (new_sr & (uint32_t)StatusReg::FR) {
|
||||||
|
// FR = 1, odd single floats point to their own registers
|
||||||
|
ctx->f_odd = &ctx->f1.u32l;
|
||||||
|
ctx->mips3_float_mode = true;
|
||||||
|
}
|
||||||
|
// Otherwise, it was cleared
|
||||||
|
else {
|
||||||
|
// FR = 0, odd single floats point to the upper half of the previous register
|
||||||
|
ctx->f_odd = &ctx->f0.u32h;
|
||||||
|
ctx->mips3_float_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the FR bit from the changed bits as it's been handled
|
||||||
|
changed &= ~(uint32_t)StatusReg::FR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any other bits were changed, assert false as they're not handled currently
|
||||||
|
if (changed) {
|
||||||
|
printf("Unhandled status register bits changed: 0x%08X\n", changed);
|
||||||
|
assert(false);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status register in the context
|
||||||
|
ctx->status_reg = new_sr;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" gpr cop0_status_read(recomp_context* ctx) {
|
||||||
|
return (gpr)(int32_t)ctx->status_reg;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||||
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||||
|
assert(false);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void do_break(uint32_t vram) {
|
extern "C" void do_break(uint32_t vram) {
|
||||||
printf("Encountered break at original vram 0x%08X\n", vram);
|
printf("Encountered break at original vram 0x%08X\n", vram);
|
||||||
|
assert(false);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +100,8 @@ void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t ar
|
|||||||
recomp_context ctx{};
|
recomp_context ctx{};
|
||||||
ctx.r29 = sp;
|
ctx.r29 = sp;
|
||||||
ctx.r4 = arg;
|
ctx.r4 = arg;
|
||||||
|
ctx.mips3_float_mode = 0;
|
||||||
|
ctx.f_odd = &ctx.f0.u32h;
|
||||||
recomp_func_t* func = get_function(addr);
|
recomp_func_t* func = get_function(addr);
|
||||||
func(rdram, &ctx);
|
func(rdram, &ctx);
|
||||||
}
|
}
|
||||||
@ -72,10 +119,6 @@ void init_overlays();
|
|||||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
|
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
|
||||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::unique_ptr<uint8_t[]> rdram_buffer;
|
std::unique_ptr<uint8_t[]> rdram_buffer;
|
||||||
recomp_context context{};
|
recomp_context context{};
|
||||||
|
|
||||||
@ -124,6 +167,10 @@ EXPORT extern "C" void init() {
|
|||||||
// Set up stack pointer
|
// Set up stack pointer
|
||||||
context.r29 = 0xFFFFFFFF803FFFF0u;
|
context.r29 = 0xFFFFFFFF803FFFF0u;
|
||||||
|
|
||||||
|
// Set up context floats
|
||||||
|
context.f_odd = &context.f0.u32h;
|
||||||
|
context.mips3_float_mode = false;
|
||||||
|
|
||||||
// Initialize variables normally set by IPL3
|
// Initialize variables normally set by IPL3
|
||||||
constexpr int32_t osTvType = 0x80000300;
|
constexpr int32_t osTvType = 0x80000300;
|
||||||
constexpr int32_t osRomType = 0x80000304;
|
constexpr int32_t osRomType = 0x80000304;
|
||||||
@ -140,10 +187,37 @@ EXPORT extern "C" void init() {
|
|||||||
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT extern "C" void start(void* window_handle, const Multilibultra::audio_callbacks_t* audio_callbacks, const Multilibultra::input_callbacks_t* input_callbacks) {
|
// LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||||
|
// return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*EXPORT extern "C"*/ void start(Multilibultra::WindowHandle window_handle, const Multilibultra::audio_callbacks_t* audio_callbacks, const Multilibultra::input_callbacks_t* input_callbacks) {
|
||||||
Multilibultra::set_audio_callbacks(audio_callbacks);
|
Multilibultra::set_audio_callbacks(audio_callbacks);
|
||||||
Multilibultra::set_input_callbacks(input_callbacks);
|
Multilibultra::set_input_callbacks(input_callbacks);
|
||||||
std::thread game_thread{[](void* window_handle) {
|
|
||||||
|
//// Register window class.
|
||||||
|
//WNDCLASS wc;
|
||||||
|
//memset(&wc, 0, sizeof(WNDCLASS));
|
||||||
|
//wc.lpfnWndProc = WindowProc;
|
||||||
|
//wc.hInstance = GetModuleHandle(0);
|
||||||
|
//wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
|
||||||
|
//wc.lpszClassName = "RT64Sample";
|
||||||
|
//RegisterClass(&wc);
|
||||||
|
|
||||||
|
//// Create window.
|
||||||
|
//const int Width = 1280;
|
||||||
|
//const int Height = 720;
|
||||||
|
//RECT rect;
|
||||||
|
//UINT dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
|
||||||
|
//rect.left = (GetSystemMetrics(SM_CXSCREEN) - Width) / 2;
|
||||||
|
//rect.top = (GetSystemMetrics(SM_CYSCREEN) - Height) / 2;
|
||||||
|
//rect.right = rect.left + Width;
|
||||||
|
//rect.bottom = rect.top + Height;
|
||||||
|
//AdjustWindowRectEx(&rect, dwStyle, 0, 0);
|
||||||
|
|
||||||
|
//HWND hwnd = CreateWindow(wc.lpszClassName, "Recomp", dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0, 0, wc.hInstance, NULL);
|
||||||
|
|
||||||
|
std::thread game_thread{[](Multilibultra::WindowHandle window_handle) {
|
||||||
debug_printf("[Recomp] Starting\n");
|
debug_printf("[Recomp] Starting\n");
|
||||||
|
|
||||||
Multilibultra::set_native_thread_name("Game Start Thread");
|
Multilibultra::set_native_thread_name("Game Start Thread");
|
||||||
@ -157,29 +231,3 @@ EXPORT extern "C" void start(void* window_handle, const Multilibultra::audio_cal
|
|||||||
|
|
||||||
game_thread.detach();
|
game_thread.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Set up console output to accept UTF-8 on windows
|
|
||||||
SetConsoleOutputCP(CP_UTF8);
|
|
||||||
|
|
||||||
// Change to a font that supports Japanese characters
|
|
||||||
CONSOLE_FONT_INFOEX cfi;
|
|
||||||
cfi.cbSize = sizeof cfi;
|
|
||||||
cfi.nFont = 0;
|
|
||||||
cfi.dwFontSize.X = 0;
|
|
||||||
cfi.dwFontSize.Y = 16;
|
|
||||||
cfi.FontFamily = FF_DONTCARE;
|
|
||||||
cfi.FontWeight = FW_NORMAL;
|
|
||||||
wcscpy_s(cfi.FaceName, L"NSimSun");
|
|
||||||
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
|
|
||||||
#else
|
|
||||||
std::setlocale(LC_ALL, "en_US.UTF-8");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
init();
|
|
||||||
start(nullptr, nullptr, nullptr);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
@ -44,7 +44,7 @@ void dummy_check_interrupts() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle) {
|
void RT64Init(uint8_t* rom, uint8_t* rdram, Multilibultra::WindowHandle window_handle) {
|
||||||
// Dynamic loading
|
// Dynamic loading
|
||||||
//auto RT64 = LoadLibrary("RT64.dll");
|
//auto RT64 = LoadLibrary("RT64.dll");
|
||||||
//if (RT64 == 0) {
|
//if (RT64 == 0) {
|
||||||
@ -57,8 +57,8 @@ void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle) {
|
|||||||
//GET_FUNC(RT64, UpdateScreen);
|
//GET_FUNC(RT64, UpdateScreen);
|
||||||
|
|
||||||
GFX_INFO gfx_info{};
|
GFX_INFO gfx_info{};
|
||||||
gfx_info.hWnd = window_handle;
|
// gfx_info.hWnd = window_handle;
|
||||||
gfx_info.hStatusBar = nullptr;
|
// gfx_info.hStatusBar = nullptr;
|
||||||
|
|
||||||
gfx_info.HEADER = rom;
|
gfx_info.HEADER = rom;
|
||||||
gfx_info.RDRAM = rdram;
|
gfx_info.RDRAM = rdram;
|
||||||
@ -93,7 +93,11 @@ void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle) {
|
|||||||
|
|
||||||
gfx_info.CheckInterrupts = dummy_check_interrupts;
|
gfx_info.CheckInterrupts = dummy_check_interrupts;
|
||||||
|
|
||||||
InitiateGFX(gfx_info);
|
gfx_info.version = 2;
|
||||||
|
gfx_info.SP_STATUS_REG = &SP_STATUS_REG;
|
||||||
|
gfx_info.RDRAM_SIZE = &RDRAM_SIZE;
|
||||||
|
|
||||||
|
InitiateGFXLinux(gfx_info, window_handle.window, window_handle.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RT64SendDL(uint8_t* rdram, const OSTask* task) {
|
void RT64SendDL(uint8_t* rdram, const OSTask* task) {
|
||||||
|
@ -36,3 +36,12 @@ extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
|||||||
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
osViSetMode(rdram, (int32_t)ctx->r4);
|
osViSetMode(rdram, (int32_t)ctx->r4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern uint64_t total_vis;
|
||||||
|
|
||||||
|
extern "C" void wait_one_frame(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
uint64_t cur_vis = total_vis;
|
||||||
|
while (cur_vis == total_vis) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user