Moved window event handling out of gfx thread so resizing doesn't freeze gameplay, added full document hot reloading for UI

This commit is contained in:
Mr-Wiseguy 2023-11-04 12:45:22 -04:00
parent 458ccd81fc
commit 346e298eb1
10 changed files with 104 additions and 86 deletions

View File

@ -64,7 +64,7 @@ typedef struct {
//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
#if defined(_WIN32)
extern "C" int InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd);
extern "C" int InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd, DWORD threadId);
#elif defined(__ANDROID__)
static_assert(false && "Unimplemented");
#elif defined(__linux__)

View File

@ -6,10 +6,8 @@ static uint32_t sample_rate = 48000;
static Multilibultra::audio_callbacks_t audio_callbacks;
void Multilibultra::set_audio_callbacks(const audio_callbacks_t* callbacks) {
if (callbacks != nullptr) {
audio_callbacks = *callbacks;
}
void set_audio_callbacks(const Multilibultra::audio_callbacks_t& callbacks) {
audio_callbacks = callbacks;
}
void Multilibultra::init_audio() {

View File

@ -236,14 +236,6 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea
}
}
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;
Multilibultra::gfx_callbacks_t::gfx_data_t gfx_data{};
@ -251,14 +243,6 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read
Multilibultra::set_native_thread_name("Gfx Thread");
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);
rsp_constants_init();
@ -285,9 +269,6 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read
RT64UpdateScreen(swap_action->origin);
}
}
if (gfx_callbacks.update_gfx != nullptr) {
gfx_callbacks.update_gfx(nullptr);
}
}
}

View File

@ -30,13 +30,18 @@ namespace Multilibultra {
#if defined(_WIN32)
// Native HWND handle to the target window.
using WindowHandle = HWND;
struct WindowHandle {
HWND window;
DWORD thread_id = (DWORD)-1;
auto operator<=>(const WindowHandle&) const = default;
};
#elif defined(__ANDROID__)
using WindowHandle = ANativeWindow*;
#elif defined(__linux__)
struct WindowHandle {
Display* display;
Window window;
auto operator<=>(const WindowHandle&) const = default;
};
#endif
@ -101,14 +106,12 @@ struct audio_callbacks_t {
get_samples_remaining_t* get_frames_remaining;
set_frequency_t* set_frequency;
};
void set_audio_callbacks(const audio_callbacks_t* callbacks);
// Input
struct input_callbacks_t {
using get_input_t = void(uint16_t*, float*, float*);
get_input_t* get_input;
};
void set_input_callbacks(const input_callbacks_t* callback);
struct gfx_callbacks_t {
using gfx_data_t = void*;
@ -119,7 +122,7 @@ struct gfx_callbacks_t {
create_window_t* create_window;
update_gfx_t* update_gfx;
};
void set_gfx_callbacks(const gfx_callbacks_t* callbacks);
void start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks);
class preemption_guard {
public:

View File

@ -7,7 +7,7 @@
#include "multilibultra.hpp"
// 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_time = std::chrono::system_clock::now();
// Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
@ -52,11 +52,11 @@ std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
}
std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) {
return start + ticks_to_duration(ticks);
return start_time + ticks_to_duration(ticks);
}
uint64_t time_now() {
return duration_to_ticks(std::chrono::system_clock::now() - start);
return duration_to_ticks(std::chrono::system_clock::now() - start_time);
}
void timer_thread(RDRAM_ARG1) {
@ -142,11 +142,11 @@ uint32_t Multilibultra::get_speed_multiplier() {
}
std::chrono::system_clock::time_point Multilibultra::get_start() {
return start;
return start_time;
}
std::chrono::system_clock::duration Multilibultra::time_since_start() {
return std::chrono::system_clock::now() - start;
return std::chrono::system_clock::now() - start_time;
}
extern "C" u32 osGetCount() {

View File

@ -3,10 +3,8 @@
static Multilibultra::input_callbacks_t input_callbacks;
void Multilibultra::set_input_callbacks(const input_callbacks_t* callbacks) {
if (callbacks != nullptr) {
input_callbacks = *callbacks;
}
void set_input_callbacks(const Multilibultra::input_callbacks_t& callbacks) {
input_callbacks = callbacks;
}
static int max_controllers = 0;

View File

@ -164,7 +164,7 @@ Multilibultra::WindowHandle create_window(Multilibultra::gfx_callbacks_t::gfx_da
SDL_GetWindowWMInfo(window, &wmInfo);
#if defined(_WIN32)
return wmInfo.info.win.window;
return Multilibultra::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
#elif defined(__ANDROID__)
static_assert(false && "Unimplemented");
#elif defined(__linux__)
@ -353,19 +353,7 @@ int main(int argc, char** argv) {
.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);
}
Multilibultra::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
return EXIT_SUCCESS;
}

View File

@ -187,31 +187,31 @@ EXPORT extern "C" void init() {
// 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_input_callbacks(input_callbacks);
static Multilibultra::gfx_callbacks_t gfx_callbacks;
//// 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);
void set_audio_callbacks(const Multilibultra::audio_callbacks_t& callbacks);
void set_input_callbacks(const Multilibultra::input_callbacks_t& callback);
//// 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);
void Multilibultra::start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks_) {
set_audio_callbacks(audio_callbacks);
set_input_callbacks(input_callbacks);
//HWND hwnd = CreateWindow(wc.lpszClassName, "Recomp", dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0, 0, wc.hInstance, NULL);
gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
gfx_callbacks_t::gfx_data_t gfx_data{};
if (gfx_callbacks.create_gfx) {
gfx_data = gfx_callbacks.create_gfx();
}
if (window_handle == WindowHandle{}) {
if (gfx_callbacks.create_window) {
window_handle = gfx_callbacks.create_window(gfx_data);
}
else {
assert(false && "No create_window callback provided");
}
}
std::thread game_thread{[](Multilibultra::WindowHandle window_handle) {
debug_printf("[Recomp] Starting\n");
@ -225,5 +225,11 @@ EXPORT extern "C" void init() {
debug_printf("[Recomp] Quitting\n");
}, window_handle};
game_thread.detach();
while (true) {
using namespace std::chrono_literals;
std::this_thread::sleep_for(1ms);
if (gfx_callbacks.update_gfx != nullptr) {
gfx_callbacks.update_gfx(nullptr);
}
}
}

View File

@ -100,7 +100,7 @@ void RT64Init(uint8_t* rom, uint8_t* rdram, Multilibultra::WindowHandle window_h
gfx_info.RDRAM_SIZE = &RDRAM_SIZE;
#if defined(_WIN32)
InitiateGFXWindows(gfx_info, window_handle);
InitiateGFXWindows(gfx_info, window_handle.window, window_handle.thread_id);
#elif defined(__ANDROID__)
static_assert(false && "Unimplemented");
#elif defined(__linux__)

View File

@ -38,6 +38,7 @@
struct UIRenderContext {
RT64::RenderInterface* interface;
RT64::RenderDevice* device;
Rml::ElementDocument* document;
};
// TODO deduplicate from rt64_common.h
@ -99,6 +100,8 @@ T from_bytes_le(const char* input) {
return *reinterpret_cast<const T*>(input);
}
void load_document();
class RmlRenderInterface_RT64 : public Rml::RenderInterface {
static constexpr uint32_t per_frame_descriptor_set = 0;
static constexpr uint32_t per_draw_descriptor_set = 1;
@ -516,7 +519,7 @@ public:
mvp_ = projection_mtx_ * transform_;
}
void start(RT64::RenderCommandList* list, uint32_t image_width, uint32_t image_height) {
void start(RT64::RenderCommandList* list, uint32_t image_width, uint32_t image_height, bool reload_style) {
list_ = list;
list_->setPipeline(pipeline_.get());
list_->setGraphicsPipelineLayout(layout_.get());
@ -528,6 +531,10 @@ public:
// Clear out any stale buffers from the last command list.
stale_buffers_.clear();
if (reload_style) {
load_document();
}
// Reset and map the upload buffer.
upload_buffer_bytes_used_ = 0;
upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map());
@ -556,6 +563,18 @@ struct {
// TODO make this not be global
extern SDL_Window* window;
void load_document() {
if (UIContext.render.document) {
UIContext.render.document->Close();
// Documents are owned by RmlUi, so we don't have anything to free here.
UIContext.render.document = nullptr;
}
UIContext.render.document = UIContext.rml.context->LoadDocument("assets/demo.rml");
if (UIContext.render.document) {
UIContext.render.document->Show();
}
}
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
printf("RT64 hook init\n");
@ -598,10 +617,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
}
}
if (Rml::ElementDocument* document = UIContext.rml.context->LoadDocument("assets/demo.rml")) {
document->Show();
}
load_document();
}
void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_chain_texture) {
@ -613,14 +629,42 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_
// TODO process SDL events
int num_keys;
const Uint8* key_state = SDL_GetKeyboardState(&num_keys);
static bool was_reload_held = false;
bool is_reload_held = key_state[SDL_SCANCODE_F11] != 0;
bool reload_sheets = is_reload_held && !was_reload_held;
was_reload_held = is_reload_held;
static bool menu_open = false;
static bool was_toggle_menu_held = false;
bool is_toggle_menu_held = key_state[SDL_SCANCODE_M] != 0;
if (is_toggle_menu_held && !was_toggle_menu_held) {
menu_open = !menu_open;
}
was_toggle_menu_held = is_toggle_menu_held;
if (menu_open) {
int width, height;
SDL_GetWindowSizeInPixels(window, &width, &height);
UIContext.rml.render_interface->start(command_list, width, height);
UIContext.rml.render_interface->start(command_list, width, height, reload_sheets);
static int prev_width = 0;
static int prev_height = 0;
if (prev_width != width || prev_height != height) {
UIContext.rml.context->SetDimensions({ width, height });
}
prev_width = width;
prev_height = height;
UIContext.rml.context->Update();
UIContext.rml.context->Render();
UIContext.rml.render_interface->end(command_list);
}
}
void deinit_hook() {