More libultra function implementations, euc-jp decoding for print output, improved build times for output project

This commit is contained in:
Mr-Wiseguy 2023-01-16 23:01:21 -05:00
parent c6de2b6189
commit d2603ce07c
26 changed files with 30090 additions and 238 deletions

View file

@ -6,6 +6,7 @@
#include <unordered_map>
#include <utility>
#include <mutex>
#include <queue>
#include <Windows.h>
#include "SDL.h"
@ -29,6 +30,8 @@ static struct {
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
PTR(void) current_buffer = NULLPTR;
PTR(void) next_buffer = NULLPTR;
OSMesg msg = (OSMesg)0;
int retrace_count = 1;
} vi;
@ -55,7 +58,6 @@ static struct {
// The same message queue may be used for multiple events, so share a mutex for all of them
std::mutex message_mutex;
uint8_t* rdram;
std::chrono::system_clock::time_point start;
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} events_context{};
@ -89,43 +91,15 @@ extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 ret
events_context.vi.retrace_count = retrace_count;
}
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
uint64_t duration_to_count(std::chrono::system_clock::duration duration) {
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
// Units: (micros * (counts/millis)) / (micros/millis) = counts
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
return total_count;
}
extern "C" u32 osGetCount() {
uint64_t total_count = duration_to_count(std::chrono::system_clock::now() - events_context.start);
// Allow for overflows, which is how osGetCount behaves
return (uint32_t)total_count;
}
extern "C" OSTime osGetTime() {
uint64_t total_count = duration_to_count(std::chrono::system_clock::now() - events_context.start);
return total_count;
}
void vi_thread_func() {
using namespace std::chrono_literals;
events_context.start = std::chrono::system_clock::now();
uint64_t total_vis = 0;
int remaining_retraces = events_context.vi.retrace_count;
while (true) {
// Determine the next VI time (more accurate than adding 16ms each VI interrupt)
auto next = events_context.start + (total_vis * 1000000us) / (60 * speed_multiplier);
auto next = Multilibultra::get_start() + (total_vis * 1000000us) / (60 * Multilibultra::get_speed_multiplier());
//if (next > std::chrono::system_clock::now()) {
// printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
// (next - std::chrono::system_clock::now()) / 1us,
@ -136,7 +110,7 @@ void vi_thread_func() {
//}
std::this_thread::sleep_until(next);
// Calculate how many VIs have passed
uint64_t new_total_vis = ((std::chrono::system_clock::now() - events_context.start) * (60 * speed_multiplier) / 1000ms) + 1;
uint64_t new_total_vis = (Multilibultra::time_since_start() * (60 * Multilibultra::get_speed_multiplier()) / 1000ms) + 1;
if (new_total_vis > total_vis + 1) {
//printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1);
}
@ -223,7 +197,7 @@ int sdl_event_filter(void* userdata, SDL_Event* event) {
return 1;
}
void gfx_thread_func(uint8_t* rdram, uint8_t* rom) {
void event_thread_func(uint8_t* rdram, uint8_t* rom) {
using namespace std::chrono_literals;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "Failed to initialize SDL2: %s\n", SDL_GetError());
@ -234,7 +208,7 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom) {
// TODO set this window title in RT64, create the window here and send it to RT64, or something else entirely
// as the current window name visibly changes as RT64 is initialized
SDL_SetWindowTitle(window, "Recomp");
SDL_SetEventFilter(sdl_event_filter, nullptr);
//SDL_SetEventFilter(sdl_event_filter, nullptr);
while (true) {
// Try to pull an action from the queue
@ -254,19 +228,35 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom) {
std::exit(EXIT_FAILURE);
}
} else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
events_context.vi.current_buffer = events_context.vi.next_buffer;
RT64UpdateScreen(swap_action->origin);
}
}
// Handle events
SDL_PumpEvents();
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);
}
//SDL_PumpEvents();
}
}
extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) {
events_context.vi.next_buffer = frameBufPtr;
events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + 640 });
}
extern "C" PTR(void) osViGetNextFramebuffer() {
return events_context.vi.next_buffer;
}
extern "C" PTR(void) osViGetCurrentFramebuffer() {
return events_context.vi.current_buffer;
}
void Multilibultra::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) {
OSTask* task = TO_PTR(OSTask, task_);
events_context.action_queue.enqueue(SpTaskAction{ *task });
@ -280,5 +270,5 @@ void Multilibultra::send_si_message() {
void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) {
events_context.rdram = rdram;
events_context.vi.thread = std::thread{ vi_thread_func };
events_context.sp.thread = std::thread{ gfx_thread_func, rdram, rom };
events_context.sp.thread = std::thread{ event_thread_func, rdram, rom };
}

View file

@ -21,6 +21,7 @@ void preinit(uint8_t* rdram, uint8_t* rom);
void native_init();
void init_scheduler();
void init_events(uint8_t* rdram, uint8_t* rom);
void init_timers(RDRAM_ARG1);
void native_thread_init(OSThread *t);
void set_self_paused(RDRAM_ARG1);
void wait_for_resumed(RDRAM_ARG1);
@ -42,6 +43,9 @@ void set_main_thread();
bool is_game_thread();
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
void send_si_message();
uint32_t get_speed_multiplier();
std::chrono::system_clock::time_point get_start();
std::chrono::system_clock::duration time_since_start();
class preemption_guard {
public:

View file

@ -1,10 +1,16 @@
#include <cstdio>
#include <thread>
#include <cassert>
#include <string>
#include "ultra64.h"
#include "multilibultra.hpp"
// Native APIs only used to set thread names for easier debugging
#ifdef _WIN32
#include <Windows.h>
#endif
extern "C" void bootproc();
thread_local bool is_main_thread = false;
@ -41,6 +47,16 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry
thread_self = self_;
is_game_thread = true;
// Set the thread name
#ifdef _WIN32
std::wstring thread_name = L"Game Thread " + std::to_wstring(self->id);
HRESULT r;
r = SetThreadDescription(
GetCurrentThread(),
thread_name.c_str()
);
#endif
// Perform any necessary native thread initialization.
Multilibultra::native_thread_init(self);
@ -93,7 +109,7 @@ extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_f
t->priority = pri;
t->id = id;
t->state = OSThreadState::PAUSED;
t->sp = sp;
t->sp = sp - 0x10; // Set up the first stack frame
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
t->context = new UltraThreadContext{};

187
test/portultra/timer.cpp Normal file
View file

@ -0,0 +1,187 @@
#include <thread>
#include <variant>
#include <set>
#include "blockingconcurrentqueue.h"
#include "ultra64.h"
#include "multilibultra.hpp"
#include "recomp.h"
// Start time for the program
static std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
// Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
struct OSTimer {
PTR(OSTimer) unused1;
PTR(OSTimer) unused2;
OSTime interval;
OSTime timestamp;
PTR(OSMesgQueue) mq;
OSMesg msg;
};
struct AddTimerAction {
PTR(OSTask) timer;
};
struct RemoveTimerAction {
PTR(OSTimer) timer;
};
using Action = std::variant<AddTimerAction, RemoveTimerAction>;
struct {
std::thread thread;
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} timer_context;
uint64_t duration_to_ticks(std::chrono::system_clock::duration duration) {
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
// Units: (micros * (counts/millis)) / (micros/millis) = counts
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
return total_count;
}
std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
using namespace std::chrono_literals;
return ticks * 1000us / counter_per_ms;
}
std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) {
return start + ticks_to_duration(ticks);
}
uint64_t time_now() {
return duration_to_ticks(std::chrono::system_clock::now() - start);
}
void timer_thread(RDRAM_ARG1) {
// Lambda comparator function to keep the set ordered
auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) {
OSTimer* a = TO_PTR(OSTimer, a_);
OSTimer* b = TO_PTR(OSTimer, b_);
// Order by timestamp if the timers have different timestamps
if (a->timestamp != b->timestamp) {
return a->timestamp < b->timestamp;
}
// If they have the exact same timestamp then order by address instead
return a < b;
};
// Ordered set of timers that are currently active
std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort};
// Lambda to process a timer action to handle adding and removing timers
auto process_timer_action = [&](const Action& action) {
// Determine the action type and act on it
if (const auto* add_action = std::get_if<AddTimerAction>(&action)) {
active_timers.insert(add_action->timer);
} else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) {
active_timers.erase(remove_action->timer);
}
};
while (true) {
// Empty the action queue
Action cur_action;
while (timer_context.action_queue.try_dequeue(cur_action)) {
process_timer_action(cur_action);
}
// If there's no timer to act on, wait for one to come in from the action queue
while (active_timers.empty()) {
timer_context.action_queue.wait_dequeue(cur_action);
process_timer_action(cur_action);
}
// Get the timer that's closest to running out
PTR(OSTimer) cur_timer_ = *active_timers.begin();
OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_);
// Remove the timer from the queue (it may get readded if waiting is interrupted)
active_timers.erase(cur_timer_);
// Determine how long to wait to reach the timer's timestamp
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::system_clock::now();
auto wait_us = std::chrono::duration_cast<std::chrono::microseconds>(wait_duration);
// Wait for either the duration to complete or a new action to come through
if (timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
// Timer was interrupted by a new action
// Add the current timer back to the queue (done first in case the action is to remove this timer)
active_timers.insert(cur_timer_);
// Process the new action
process_timer_action(cur_action);
} else {
// Waiting for the timer completed, so send the timer's message to its message queue
osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK);
// If the timer has a specified interval then reload it with that value
if (cur_timer->interval != 0) {
cur_timer->timestamp = cur_timer->interval + time_now();
active_timers.insert(cur_timer_);
}
}
}
}
void Multilibultra::init_timers(RDRAM_ARG1) {
timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 };
}
uint32_t Multilibultra::get_speed_multiplier() {
return speed_multiplier;
}
std::chrono::system_clock::time_point Multilibultra::get_start() {
return start;
}
std::chrono::system_clock::duration Multilibultra::time_since_start() {
return std::chrono::system_clock::now() - start;
}
extern "C" u32 osGetCount() {
uint64_t total_count = time_now();
// Allow for overflows, which is how osGetCount behaves
return (uint32_t)total_count;
}
extern "C" OSTime osGetTime() {
uint64_t total_count = time_now();
return total_count;
}
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
OSTimer* t = TO_PTR(OSTimer, t_);
// Determine the time when this timer will trigger off
if (countdown == 0) {
// Set the timestamp based on the interval
t->timestamp = interval + time_now();
} else {
t->timestamp = countdown + time_now();
}
t->interval = interval;
t->mq = mq;
t->msg = msg;
timer_context.action_queue.enqueue(AddTimerAction{ t_ });
return 0;
}
extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
timer_context.action_queue.enqueue(RemoveTimerAction{ t_ });
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
return 0;
}

View file

@ -166,8 +166,12 @@ s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32);
void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr);
PTR(void) osViGetNextFramebuffer();
PTR(void) osViGetCurrentFramebuffer();
u32 osGetCount();
OSTime osGetTime();
int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg);
int osStopTimer(RDRAM_ARG PTR(OSTimer) timer);
u32 osVirtualToPhysical(PTR(void) addr);
#ifdef __cplusplus

View file

@ -4,6 +4,7 @@
void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) {
Multilibultra::set_main_thread();
Multilibultra::init_events(rdram, rom);
Multilibultra::init_timers(rdram);
}
extern "C" void osInitialize() {