Remove librecomp usage from ultramodern (#23)

* Start setting up user callbacks system

* Implement RSP callbacks

* move RSP callbacks (and related RSP stuff) to its own file

* Move destroy_ui to gfx_callbacks_t and change recomp::start  to require rsp_callbacks_t

* error_handling.hpp

* Remove UserCallbacks in favor of threads_callbacks_t

* Add `ultramodern::set_callbacks` and some other cleanups

* Move most RSP stuff to librecomp

* Rename rsp_stuff.hpp to rsp.hpp

* Add rsp.cpp to librecomp cmake file

* review

* Remove `recomp::message_box`

* Fix missing rename
This commit is contained in:
Anghelo Carvajal 2024-05-30 22:56:45 -04:00 committed by GitHub
parent 6dce8c2fc2
commit e3e7024342
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 299 additions and 96 deletions

View file

@ -24,6 +24,7 @@ add_library(librecomp STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/pi.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/print.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/recomp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/sp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ultra_stubs.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ultra_translation.cpp"

View file

@ -5,6 +5,7 @@
#include <filesystem>
#include "recomp.h"
#include "rsp.h"
#include <ultramodern/ultramodern.hpp>
namespace recomp {
@ -38,9 +39,8 @@ namespace recomp {
void set_rom_contents(std::vector<uint8_t>&& new_rom);
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks);
void start_game(std::u8string game_id);
void message_box(const char* message);
void start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks, const ultramodern::events::callbacks_t& thread_callbacks, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_);
void start_game(const std::u8string& game_id);
std::filesystem::path get_app_folder_path();
std::u8string current_game_id();

View file

@ -1,9 +1,13 @@
#ifndef __RSP_H__
#define __RSP_H__
#include <cstdio>
#include "rsp_vu.h"
#include "recomp.h"
#include <cstdio>
#include "ultramodern/ultra64.h"
// TODO: Move these to recomp namespace?
enum class RspExitReason {
Invalid,
@ -13,6 +17,8 @@ enum class RspExitReason {
Unsupported
};
using RspUcodeFunc = RspExitReason(uint8_t* rdram);
extern uint8_t dmem[];
extern uint16_t rspReciprocals[512];
extern uint16_t rspInverseSquareRoots[512];
@ -61,7 +67,7 @@ static inline void RSP_MEM_H_STORE(uint32_t offset, uint32_t addr, uint32_t val)
#define RSP_ADD32(a, b) \
((int32_t)((a) + (b)))
#define RSP_SUB32(a, b) \
((int32_t)((a) - (b)))
@ -91,4 +97,27 @@ static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_
}
}
namespace recomp {
namespace rsp {
struct callbacks_t {
using get_rsp_microcode_t = RspUcodeFunc*(const OSTask* task);
/**
* Return a function pointer to the corresponding RSP microcode function for the given `task_type`.
*
* The full OSTask (`task` parameter) is passed in case the `task_type` number is not enough information to distinguish out the exact microcode function.
*
* This function is allowed to return `nullptr` if no microcode matches the specified task. In this case a message will be printed to stderr and the program will exit.
*/
get_rsp_microcode_t* get_rsp_microcode;
};
void set_callbacks(const callbacks_t& callbacks);
void constants_init();
bool run_task(uint8_t* rdram, const OSTask* task);
}
}
#endif

View file

@ -21,7 +21,8 @@
#include "recomp_overlays.h"
#include "recomp_game.h"
#include "xxHash/xxh3.h"
#include <ultramodern/ultramodern.hpp>
#include "ultramodern/ultramodern.hpp"
#include "ultramodern/error_handling.hpp"
#ifdef _MSC_VER
inline uint32_t byteswap(uint32_t val) {
@ -402,7 +403,7 @@ std::u8string recomp::current_game_id() {
return current_game.value();
};
void recomp::start_game(std::u8string game_id) {
void recomp::start_game(const std::u8string& game_id) {
std::lock_guard<std::mutex> lock(current_game_mutex);
current_game = game_id;
game_status.store(GameStatus::Running);
@ -412,7 +413,6 @@ bool ultramodern::is_game_started() {
return game_status.load() != GameStatus::None;
}
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks);
void set_input_callbacks(const ultramodern::input_callbacks_t& callback);
std::atomic_bool exited = false;
@ -426,9 +426,18 @@ void ultramodern::quit() {
current_game.reset();
}
void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_) {
void recomp::start(ultramodern::WindowHandle window_handle, const recomp::rsp::callbacks_t& rsp_callbacks, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_, const ultramodern::events::callbacks_t& thread_callbacks_, const ultramodern::error_handling::callbacks_t& error_handling_callbacks_) {
recomp::check_all_stored_roms();
set_audio_callbacks(audio_callbacks);
recomp::rsp::set_callbacks(rsp_callbacks);
static const ultramodern::rsp::callbacks_t ultramodern_rsp_callbacks {
.init = recomp::rsp::constants_init,
.run_task = recomp::rsp::run_task,
};
ultramodern::set_callbacks(ultramodern_rsp_callbacks, audio_callbacks, input_callbacks, gfx_callbacks_, thread_callbacks_, error_handling_callbacks_);
set_input_callbacks(input_callbacks);
ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
@ -467,7 +476,7 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a
case GameStatus::Running:
{
if (!recomp::load_stored_rom(current_game.value())) {
recomp::message_box("Error opening stored ROM! Please restart this program.");
ultramodern::error_handling::message_box("Error opening stored ROM! Please restart this program.");
}
auto find_it = game_roms.find(current_game.value());
@ -499,6 +508,7 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a
gfx_callbacks.update_gfx(gfx_data);
}
}
game_thread.join();
ultramodern::join_event_threads();
ultramodern::join_thread_cleaner_thread();

62
librecomp/src/rsp.cpp Normal file
View file

@ -0,0 +1,62 @@
#include <cassert>
#include <cstring>
#include <cinttypes>
#include "rsp.h"
static recomp::rsp::callbacks_t rsp_callbacks {};
void recomp::rsp::set_callbacks(const callbacks_t& callbacks) {
rsp_callbacks = callbacks;
}
uint8_t dmem[0x1000];
uint16_t rspReciprocals[512];
uint16_t rspInverseSquareRoots[512];
// From Ares emulator. For license details, see rsp_vu.h
void recomp::rsp::constants_init() {
rspReciprocals[0] = u16(~0);
for (u16 index = 1; index < 512; index++) {
u64 a = index + 512;
u64 b = (u64(1) << 34) / a;
rspReciprocals[index] = u16((b + 1) >> 8);
}
for (u16 index = 0; index < 512; index++) {
u64 a = (index + 512) >> ((index % 2 == 1) ? 1 : 0);
u64 b = 1 << 17;
//find the largest b where b < 1.0 / sqrt(a)
while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++;
rspInverseSquareRoots[index] = u16(b >> 1);
}
}
// Runs a recompiled RSP microcode
bool recomp::rsp::run_task(uint8_t* rdram, const OSTask* task) {
assert(rsp_callbacks.get_rsp_microcode != nullptr);
RspUcodeFunc* ucode_func = rsp_callbacks.get_rsp_microcode(task);
if (ucode_func == nullptr) {
fprintf(stderr, "No registered RSP ucode for %" PRIu32 " (returned `nullptr`)\n", task->t.type);
return false;
}
// Load the OSTask into DMEM
memcpy(&dmem[0xFC0], task, sizeof(OSTask));
// Load the ucode data into DMEM
dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1);
// Run the ucode
RspExitReason exit_reason = ucode_func(rdram);
// Ensure that the ucode exited correctly
if (exit_reason != RspExitReason::Broke) {
fprintf(stderr, "RSP ucode %" PRIu32 " exited unexpectedly. exit_reason: %i\n", task->t.type, exit_reason);
assert(exit_reason == RspExitReason::Broke);
return false;
}
return true;
}

View file

@ -11,15 +11,18 @@ set(CMAKE_CXX_EXTENSIONS OFF)
add_library(ultramodern STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/audio.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/events.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/mesgqueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/misc_ultra.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/rsp.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/rt64_layer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/scheduling.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/task_win32.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/threadqueue.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/threads.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/timer.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/ultrainit.cpp"
)
target_include_directories(ultramodern PUBLIC
@ -43,11 +46,6 @@ target_include_directories(ultramodern PRIVATE
"${PROJECT_SOURCE_DIR}/../rt64/src/contrib/nativefiledialog-extended/src/include"
)
# TODO: remove when librecomp is untangled from ultramodern
target_include_directories(ultramodern PRIVATE
"${CMAKE_SOURCE_DIR}/librecomp/include"
)
if (WIN32)
include(FetchContent)
# Fetch SDL2 on windows

View file

@ -0,0 +1,23 @@
#ifndef __ERROR_HANDLING_HPP__
#define __ERROR_HANDLING_HPP__
namespace ultramodern {
namespace error_handling {
struct callbacks_t {
using message_box_t = void(const char* msg);
/**
* Show an OS dialog with the given `msg`.
*
* The `msg` parameter is always non-`nullptr`.
*/
message_box_t *message_box;
};
void set_callbacks(const callbacks_t& callbacks);
void message_box(const char* msg);
}
}
#endif

View file

@ -0,0 +1,25 @@
#ifndef __EVENTS_HPP__
#define __EVENTS_HPP__
namespace ultramodern {
namespace events {
struct callbacks_t {
using vi_callback_t = void();
using gfx_init_callback_t = void();
/**
* Called in each VI.
*/
vi_callback_t* vi_callback;
/**
* Called before entering the gfx main loop.
*/
gfx_init_callback_t* gfx_init_callback;
};
void set_callbacks(const callbacks_t& callbacks);
}
}
#endif

View file

@ -1,12 +0,0 @@
#ifndef __ULTRAMODERN_RECOMP_UI__
#define __ULTRAMODERN_RECOMP_UI__
namespace recomp {
// Currently those functions are expected to be provided by the consumer of this library.
// TODO: Change these functions to a callback registering system
void destroy_ui();
void update_supported_options();
}
#endif

View file

@ -0,0 +1,35 @@
#ifndef __RSP_STUFF_HPP__
#define __RSP_STUFF_HPP__
// TODO: rename
#include <cstdint>
#include "ultra64.h"
// TODO: Move these to ultramodern namespace?
namespace ultramodern {
namespace rsp {
struct callbacks_t {
using init_t = void();
using run_microcode_t = bool(RDRAM_ARG const OSTask* task);
init_t* init;
/**
* Executes the given RSP task.
*
* Returns true if task was executed successfully.
*/
run_microcode_t* run_task;
};
void set_callbacks(const callbacks_t& callbacks);
void init();
bool run_task(RDRAM_ARG const OSTask* task);
};
} // namespace ultramodern
#endif

View file

@ -25,6 +25,10 @@
# undef Success
#endif
#include "ultramodern/error_handling.hpp"
#include "ultramodern/events.hpp"
#include "ultramodern/rsp.hpp"
struct UltraThreadContext {
std::thread host_thread;
moodycamel::LightweightSemaphore running;
@ -132,6 +136,7 @@ struct audio_callbacks_t {
set_frequency_t* set_frequency;
};
// TODO: These functions are currently called by librecomp, but will get called by ultramodern in the future
// Input
struct input_callbacks_t {
using poll_input_t = void(void);
@ -142,21 +147,32 @@ struct input_callbacks_t {
set_rumble_t* set_rumble;
};
// TODO: Most of the members of this struct are not used by ultramodern. Should we move them to librecomp instead?
struct gfx_callbacks_t {
using gfx_data_t = void*;
using create_gfx_t = gfx_data_t();
using create_window_t = WindowHandle(gfx_data_t);
using update_gfx_t = void(gfx_data_t);
create_gfx_t* create_gfx;
create_window_t* create_window;
update_gfx_t* update_gfx;
};
bool is_game_started();
void quit();
void join_event_threads();
void join_thread_cleaner_thread();
void join_saving_thread();
void set_audio_callbacks(const audio_callbacks_t& callbacks);
/**
* Register all the callbacks used by `ultramodern`, most of them being optional.
*
* It must be called only once and it must be called before `ultramodern::preinit`.
*/
void set_callbacks(const rsp::callbacks_t& rsp_callbacks, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks, const events::callbacks_t& events_callbacks, const error_handling::callbacks_t& error_handling_callbacks);
} // namespace ultramodern
#define MIN(a, b) ((a) < (b) ? (a) : (b))

View file

@ -6,7 +6,7 @@ static uint32_t sample_rate = 48000;
static ultramodern::audio_callbacks_t audio_callbacks;
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks) {
void ultramodern::set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks) {
audio_callbacks = callbacks;
}

View file

@ -0,0 +1,19 @@
#include <cstdio>
#include "ultramodern/error_handling.hpp"
static ultramodern::error_handling::callbacks_t error_handling_callbacks{};
void ultramodern::error_handling::set_callbacks(const ultramodern::error_handling::callbacks_t& callbacks) {
error_handling_callbacks = callbacks;
}
void ultramodern::error_handling::message_box(const char* msg) {
// We print the message to stderr since the user may not have provided a message_box callback
fprintf(stderr, "%s\n", msg);
if (error_handling_callbacks.message_box != nullptr) {
error_handling_callbacks.message_box(msg);
}
}

View file

@ -12,14 +12,16 @@
#include "blockingconcurrentqueue.h"
#include "ultra64.h"
#include "ultramodern.hpp"
#include "ultramodern/ultramodern.hpp"
#include "config.hpp"
#include "rt64_layer.h"
#include "recomp_ui.h"
#include "recomp.h"
#include "recomp_game.h"
#include "recomp_input.h"
#include "rsp.h"
#include "ultramodern/rsp.hpp"
static ultramodern::events::callbacks_t events_callbacks{};
void ultramodern::events::set_callbacks(const ultramodern::events::callbacks_t& callbacks) {
events_callbacks = callbacks;
}
struct SpTaskAction {
OSTask task;
@ -116,7 +118,7 @@ void vi_thread_func() {
// the game to generate new audio and gfx lists.
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical);
using namespace std::chrono_literals;
int remaining_retraces = events_context.vi.retrace_count;
while (!exited) {
@ -176,9 +178,10 @@ void vi_thread_func() {
}
}
}
// TODO move recomp code out of ultramodern.
recomp::update_rumble();
if (events_callbacks.vi_callback != nullptr) {
events_callbacks.vi_callback();
}
}
}
@ -194,45 +197,6 @@ void dp_complete() {
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
}
uint8_t dmem[0x1000];
uint16_t rspReciprocals[512];
uint16_t rspInverseSquareRoots[512];
using RspUcodeFunc = RspExitReason(uint8_t* rdram);
extern RspUcodeFunc njpgdspMain;
extern RspUcodeFunc aspMain;
// From Ares emulator. For license details, see rsp_vu.h
void rsp_constants_init() {
rspReciprocals[0] = u16(~0);
for (u16 index = 1; index < 512; index++) {
u64 a = index + 512;
u64 b = (u64(1) << 34) / a;
rspReciprocals[index] = u16((b + 1) >> 8);
}
for (u16 index = 0; index < 512; index++) {
u64 a = (index + 512) >> ((index % 2 == 1) ? 1 : 0);
u64 b = 1 << 17;
//find the largest b where b < 1.0 / sqrt(a)
while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++;
rspInverseSquareRoots[index] = u16(b >> 1);
}
}
// Runs a recompiled RSP microcode
void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) {
// Load the OSTask into DMEM
memcpy(&dmem[0xFC0], task, sizeof(OSTask));
// Load the ucode data into DMEM
dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1);
// Run the ucode
RspExitReason exit_reason = ucode_func(rdram);
// Ensure that the ucode exited correctly
assert(exit_reason == RspExitReason::Broke);
}
void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready) {
ultramodern::set_native_thread_name("SP Task Thread");
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal);
@ -249,15 +213,9 @@ void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_r
return;
}
// Run the correct function based on the task type
if (task->t.type == M_AUDTASK) {
run_rsp_microcode(rdram, task, aspMain);
}
else if (task->t.type == M_NJPEGTASK) {
run_rsp_microcode(rdram, task, njpgdspMain);
}
else {
fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task->t.type);
if (!ultramodern::rsp::run_task(PASS_RDRAM task)) {
fprintf(stderr, "Failed to execute task type: %" PRIu32 "\n", task->t.type);
assert(false);
std::quick_exit(EXIT_FAILURE);
}
@ -323,10 +281,11 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re
return;
}
// TODO move recomp code out of ultramodern.
recomp::update_supported_options();
if (events_callbacks.gfx_init_callback != nullptr) {
events_callbacks.gfx_init_callback();
}
rsp_constants_init();
ultramodern::rsp::init();
// Notify the caller thread that this thread is ready.
thread_ready->signal();
@ -372,8 +331,7 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re
}
}
}
// TODO move recomp code out of ultramodern.
recomp::destroy_ui();
rt64.shutdown();
}
@ -571,7 +529,7 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle)
events_context.rdram = rdram;
events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, &gfx_thread_ready, window_handle };
events_context.sp.task_thread = std::thread{ task_thread_func, rdram, &task_thread_ready };
// Wait for the two sp threads to be ready before continuing to prevent the game from
// running before we're able to handle RSP tasks.
gfx_thread_ready.wait();
@ -580,9 +538,11 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle)
ultramodern::RT64SetupResult setup_result = rt64_setup_result.load();
if (rt64_setup_result != ultramodern::RT64SetupResult::Success) {
auto show_rt64_error = [](const std::string& msg) {
// TODO move recomp code out of ultramodern (message boxes).
recomp::message_box(("An error has been encountered on startup: " + msg).c_str());
std::string error_msg = "An error has been encountered on startup: " + msg;
ultramodern::error_handling::message_box(error_msg.c_str());
};
const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date.";
switch (rt64_setup_result) {
case ultramodern::RT64SetupResult::DynamicLibrariesNotFound:

View file

@ -4,7 +4,6 @@
#include "ultra64.h"
#include "ultramodern.hpp"
#include "recomp.h"
struct QueuedMessage {
PTR(OSMesgQueue) mq;

22
ultramodern/src/rsp.cpp Normal file
View file

@ -0,0 +1,22 @@
#include <cassert>
#include <cstring>
#include "ultramodern/rsp.hpp"
static ultramodern::rsp::callbacks_t rsp_callbacks {};
void ultramodern::rsp::set_callbacks(const callbacks_t& callbacks) {
rsp_callbacks = callbacks;
}
void ultramodern::rsp::init() {
if (rsp_callbacks.init != nullptr) {
rsp_callbacks.init();
}
}
bool ultramodern::rsp::run_task(RDRAM_ARG const OSTask* task) {
assert(rsp_callbacks.run_task != nullptr);
return rsp_callbacks.run_task(PASS_RDRAM task);
}

View file

@ -1,6 +1,22 @@
#include "ultra64.h"
#include "ultramodern.hpp"
void ultramodern::set_callbacks(
const rsp::callbacks_t& rsp_callbacks,
const audio_callbacks_t& audio_callbacks,
const input_callbacks_t& input_callbacks,
const gfx_callbacks_t& gfx_callbacks,
const events::callbacks_t& thread_callbacks,
const error_handling::callbacks_t& error_handling_callbacks
) {
ultramodern::rsp::set_callbacks(rsp_callbacks);
ultramodern::set_audio_callbacks(audio_callbacks);
(void)input_callbacks; // nothing yet
(void)gfx_callbacks; // nothing yet
ultramodern::events::set_callbacks(thread_callbacks);
ultramodern::error_handling::set_callbacks(error_handling_callbacks);
}
void ultramodern::preinit(RDRAM_ARG ultramodern::WindowHandle window_handle) {
ultramodern::set_main_thread();
ultramodern::init_events(PASS_RDRAM window_handle);