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; //DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
#if defined(_WIN32) #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__) #elif defined(__ANDROID__)
static_assert(false && "Unimplemented"); static_assert(false && "Unimplemented");
#elif defined(__linux__) #elif defined(__linux__)

View File

@ -6,10 +6,8 @@ static uint32_t sample_rate = 48000;
static Multilibultra::audio_callbacks_t audio_callbacks; static Multilibultra::audio_callbacks_t audio_callbacks;
void Multilibultra::set_audio_callbacks(const audio_callbacks_t* callbacks) { void set_audio_callbacks(const Multilibultra::audio_callbacks_t& callbacks) {
if (callbacks != nullptr) { audio_callbacks = callbacks;
audio_callbacks = *callbacks;
}
} }
void Multilibultra::init_audio() { 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) { 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::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_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();
@ -285,9 +269,6 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read
RT64UpdateScreen(swap_action->origin); 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) #if defined(_WIN32)
// Native HWND handle to the target window. // 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__) #elif defined(__ANDROID__)
using WindowHandle = ANativeWindow*; using WindowHandle = ANativeWindow*;
#elif defined(__linux__) #elif defined(__linux__)
struct WindowHandle { struct WindowHandle {
Display* display; Display* display;
Window window; Window window;
auto operator<=>(const WindowHandle&) const = default;
}; };
#endif #endif
@ -101,14 +106,12 @@ struct audio_callbacks_t {
get_samples_remaining_t* get_frames_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);
// Input // Input
struct input_callbacks_t { struct input_callbacks_t {
using get_input_t = void(uint16_t*, float*, float*); using get_input_t = void(uint16_t*, float*, float*);
get_input_t* get_input; get_input_t* get_input;
}; };
void set_input_callbacks(const input_callbacks_t* callback);
struct gfx_callbacks_t { struct gfx_callbacks_t {
using gfx_data_t = void*; using gfx_data_t = void*;
@ -119,7 +122,7 @@ struct gfx_callbacks_t {
create_window_t* create_window; create_window_t* create_window;
update_gfx_t* update_gfx; 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 { class preemption_guard {
public: public:

View File

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

View File

@ -3,10 +3,8 @@
static Multilibultra::input_callbacks_t input_callbacks; static Multilibultra::input_callbacks_t input_callbacks;
void Multilibultra::set_input_callbacks(const input_callbacks_t* callbacks) { void set_input_callbacks(const Multilibultra::input_callbacks_t& callbacks) {
if (callbacks != nullptr) { input_callbacks = callbacks;
input_callbacks = *callbacks;
}
} }
static int max_controllers = 0; 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); SDL_GetWindowWMInfo(window, &wmInfo);
#if defined(_WIN32) #if defined(_WIN32)
return wmInfo.info.win.window; return Multilibultra::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
static_assert(false && "Unimplemented"); static_assert(false && "Unimplemented");
#elif defined(__linux__) #elif defined(__linux__)
@ -353,19 +353,7 @@ int main(int argc, char** argv) {
.get_input = get_input, .get_input = get_input,
}; };
//create_gfx(); Multilibultra::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
//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; return EXIT_SUCCESS;
} }

View File

@ -187,31 +187,31 @@ EXPORT extern "C" void init() {
// return DefWindowProc(hwnd, uMsg, wParam, 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) { static Multilibultra::gfx_callbacks_t gfx_callbacks;
Multilibultra::set_audio_callbacks(audio_callbacks);
Multilibultra::set_input_callbacks(input_callbacks);
//// Register window class. void set_audio_callbacks(const Multilibultra::audio_callbacks_t& callbacks);
//WNDCLASS wc; void set_input_callbacks(const Multilibultra::input_callbacks_t& callback);
//memset(&wc, 0, sizeof(WNDCLASS));
//wc.lpfnWndProc = WindowProc;
//wc.hInstance = GetModuleHandle(0);
//wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
//wc.lpszClassName = "RT64Sample";
//RegisterClass(&wc);
//// Create window. void Multilibultra::start(WindowHandle window_handle, const audio_callbacks_t& audio_callbacks, const input_callbacks_t& input_callbacks, const gfx_callbacks_t& gfx_callbacks_) {
//const int Width = 1280; set_audio_callbacks(audio_callbacks);
//const int Height = 720; set_input_callbacks(input_callbacks);
//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); 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) { std::thread game_thread{[](Multilibultra::WindowHandle window_handle) {
debug_printf("[Recomp] Starting\n"); debug_printf("[Recomp] Starting\n");
@ -225,5 +225,11 @@ EXPORT extern "C" void init() {
debug_printf("[Recomp] Quitting\n"); debug_printf("[Recomp] Quitting\n");
}, window_handle}; }, 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; gfx_info.RDRAM_SIZE = &RDRAM_SIZE;
#if defined(_WIN32) #if defined(_WIN32)
InitiateGFXWindows(gfx_info, window_handle); InitiateGFXWindows(gfx_info, window_handle.window, window_handle.thread_id);
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
static_assert(false && "Unimplemented"); static_assert(false && "Unimplemented");
#elif defined(__linux__) #elif defined(__linux__)

View File

@ -38,6 +38,7 @@
struct UIRenderContext { struct UIRenderContext {
RT64::RenderInterface* interface; RT64::RenderInterface* interface;
RT64::RenderDevice* device; RT64::RenderDevice* device;
Rml::ElementDocument* document;
}; };
// TODO deduplicate from rt64_common.h // TODO deduplicate from rt64_common.h
@ -99,6 +100,8 @@ T from_bytes_le(const char* input) {
return *reinterpret_cast<const T*>(input); return *reinterpret_cast<const T*>(input);
} }
void load_document();
class RmlRenderInterface_RT64 : public Rml::RenderInterface { class RmlRenderInterface_RT64 : public Rml::RenderInterface {
static constexpr uint32_t per_frame_descriptor_set = 0; static constexpr uint32_t per_frame_descriptor_set = 0;
static constexpr uint32_t per_draw_descriptor_set = 1; static constexpr uint32_t per_draw_descriptor_set = 1;
@ -516,7 +519,7 @@ public:
mvp_ = projection_mtx_ * transform_; 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_ = list;
list_->setPipeline(pipeline_.get()); list_->setPipeline(pipeline_.get());
list_->setGraphicsPipelineLayout(layout_.get()); list_->setGraphicsPipelineLayout(layout_.get());
@ -528,6 +531,10 @@ public:
// Clear out any stale buffers from the last command list. // Clear out any stale buffers from the last command list.
stale_buffers_.clear(); stale_buffers_.clear();
if (reload_style) {
load_document();
}
// Reset and map the upload buffer. // Reset and map the upload buffer.
upload_buffer_bytes_used_ = 0; upload_buffer_bytes_used_ = 0;
upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map()); upload_buffer_mapped_data_ = reinterpret_cast<uint8_t*>(upload_buffer_->map());
@ -556,6 +563,18 @@ struct {
// TODO make this not be global // TODO make this not be global
extern SDL_Window* window; 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) { void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
printf("RT64 hook init\n"); 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")) { load_document();
document->Show();
}
} }
void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_chain_texture) { void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_chain_texture) {
@ -613,13 +629,41 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_
// TODO process SDL events // TODO process SDL events
int width, height; int num_keys;
SDL_GetWindowSizeInPixels(window, &width, &height); const Uint8* key_state = SDL_GetKeyboardState(&num_keys);
UIContext.rml.render_interface->start(command_list, width, height); static bool was_reload_held = false;
UIContext.rml.context->Update(); bool is_reload_held = key_state[SDL_SCANCODE_F11] != 0;
UIContext.rml.context->Render(); bool reload_sheets = is_reload_held && !was_reload_held;
UIContext.rml.render_interface->end(command_list); 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, 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() { void deinit_hook() {