diff --git a/CMakeLists.txt b/CMakeLists.txt index d35c226..5ad926b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,7 +127,9 @@ target_include_directories(MMRecomp PRIVATE ${CMAKE_SOURCE_DIR}/lib/concurrentqueue ${CMAKE_SOURCE_DIR}/lib/RmlUi/Include ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends + ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/contrib ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/contrib/mupen64plus-win32-deps/SDL2-2.26.3/include + ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/contrib/hlslpp/include ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render diff --git a/assets/config_menu.rml b/assets/config_menu.rml index bfb9ead..e1a775e 100644 --- a/assets/config_menu.rml +++ b/assets/config_menu.rml @@ -33,55 +33,55 @@
Graphics - +
-
+

- + - + - +
-
+

- + - +
-
+

- + - +
-
+

- + - + - + - +
@@ -89,20 +89,24 @@
-
+

-
- +
+ - + - + -
- +
+
+ +
+ +
diff --git a/assets/recomp.rcss b/assets/recomp.rcss index 2d90194..9190b20 100644 --- a/assets/recomp.rcss +++ b/assets/recomp.rcss @@ -33,7 +33,7 @@ div.option_container { margin-left:auto; margin-right:auto; font-effect: outline(2dp black); - font-size:24dp; + font-size:20dp; background:rgba(50,50,50,200); } @@ -55,6 +55,11 @@ div.option_list { justify-content:center; } +label.option_title { + padding:4dp; + font-size:24dp; +} + div#title_bar { z-index: 1; position: absolute; @@ -137,17 +142,18 @@ input.submit { /* vertical-align: center; */ height: auto; width: 100%; + focus:auto; + tab-index:auto; + nav-up:auto; + nav-down:auto; + nav-right:auto; + nav-left:auto; } button:focus, input.submit:focus { - font-effect: blur(3dp #fff); - background-color: rgb(120, 120, 120); -} - -button:hover, -input.submit:hover { - background-color: rgb(150, 150, 150); + /* font-effect: blur(3dp #fff); */ + color: #329696; } button:active, @@ -161,6 +167,34 @@ input.submit:disabled { cursor: unavailable } +button[disabled] { + /* decorator: image(button); */ + /* image-color: #329696; */ + /* color:black; */ + color:rgb(100,100,100); + background-color: rgb(50, 50, 50); + /* focus:none; + tab-index:none; + nav-up:none; + nav-down:none; + nav-right:none; + nav-left:none; */ +} + +button:focus[disabled] { + /* decorator: image(button); */ + /* image-color: #329696; */ + /* color:black; */ + color:#329696; + background-color: rgb(50, 50, 50); + /* focus:none; + tab-index:none; + nav-up:none; + nav-down:none; + nav-right:none; + nav-left:none; */ +} + input.text, input.password { box-sizing: border-box; @@ -316,14 +350,6 @@ input.radio + label { /* decorator: image(radio) */ } -#rr_manual:not(:checked) ~ #rr_manual_input { - /* background: rgb(120,120,120); */ - /* font-effect: outline(2dp black); */ - display:none; - /* display:none; */ - /* decorator: image(radio) */ -} - input.radio:checked + label { /* background: rgb(72, 102, 255); */ color: white; @@ -403,12 +429,6 @@ input.checkbox:checked:active { decorator: image(checkbox-checked-active) } */ -input.range { - height: 32dp; - margin:auto; - width:80%; -} - input.range slidertrack { margin-top: 7dp; height: 6dp; diff --git a/include/recomp_ui.h b/include/recomp_ui.h index 33bf92f..4c291f0 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -8,6 +8,7 @@ namespace Rml { class ElementDocument; class EventListenerInstancer; + class Context; } namespace recomp { @@ -16,6 +17,7 @@ namespace recomp { bool try_deque_event(SDL_Event& out); std::unique_ptr make_event_listener_instancer(); + void make_ui_bindings(Rml::Context* context); enum class Menu { Launcher, diff --git a/include/rt64_layer.h b/include/rt64_layer.h index e63940e..3840c12 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -2,81 +2,22 @@ #define __RT64_LAYER_H__ #include "../ultramodern/ultramodern.hpp" +#include "../ultramodern/config.hpp" -typedef struct { - // void* hWnd; - // void* hStatusBar; +namespace RT64 { + struct Application; +} - // int Reserved; +namespace ultramodern { + struct WindowHandle; +} - unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */ - unsigned char* RDRAM; - unsigned char* DMEM; - unsigned char* IMEM; - - unsigned int* MI_INTR_REG; - - unsigned int* DPC_START_REG; - unsigned int* DPC_END_REG; - unsigned int* DPC_CURRENT_REG; - unsigned int* DPC_STATUS_REG; - unsigned int* DPC_CLOCK_REG; - unsigned int* DPC_BUFBUSY_REG; - unsigned int* DPC_PIPEBUSY_REG; - unsigned int* DPC_TMEM_REG; - - unsigned int* VI_STATUS_REG; - unsigned int* VI_ORIGIN_REG; - unsigned int* VI_WIDTH_REG; - unsigned int* VI_INTR_REG; - unsigned int* VI_V_CURRENT_LINE_REG; - unsigned int* VI_TIMING_REG; - unsigned int* VI_V_SYNC_REG; - unsigned int* VI_H_SYNC_REG; - unsigned int* VI_LEAP_REG; - unsigned int* VI_H_START_REG; - unsigned int* VI_V_START_REG; - unsigned int* VI_V_BURST_REG; - unsigned int* VI_X_SCALE_REG; - unsigned int* VI_Y_SCALE_REG; - - void (*CheckInterrupts)(void); - - unsigned int version; - unsigned int* SP_STATUS_REG; - const unsigned int* RDRAM_SIZE; -} GFX_INFO; - -#ifdef _WIN32 -#define DLLEXPORT extern "C" __declspec(dllexport) -#define DLLIMPORT extern "C" __declspec(dllimport) -#define CALL __cdecl -#else -#define DLLEXPORT extern "C" __attribute__((visibility("default"))) -#define DLLIMPORT extern "C" -#endif - -// Dynamic loading -//DLLEXPORT int (CALL *InitiateGFX)(GFX_INFO Gfx_Info) = nullptr; -//DLLEXPORT void (CALL *ProcessRDPList)(void) = nullptr; -//DLLEXPORT void (CALL *ProcessDList)(void) = nullptr; -//DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr; -//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr; - -#if defined(_WIN32) -extern "C" int InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd, DWORD threadId); -#elif defined(__ANDROID__) -static_assert(false && "Unimplemented"); -#elif defined(__linux__) -extern "C" int InitiateGFXLinux(GFX_INFO Gfx_Info, Window window, Display *display); -#else -static_assert(false && "Unimplemented"); -#endif -DLLIMPORT void ProcessRDPList(void); -DLLIMPORT void ProcessDList(void); -DLLIMPORT void UpdateScreen(void); -DLLIMPORT void ChangeWindow(void); -DLLIMPORT void PluginShutdown(void); +RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle); +void RT64UpdateConfig(RT64::Application* application, const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config); +void RT64SendDL(uint8_t* rdram, const OSTask* task); +void RT64UpdateScreen(uint32_t vi_origin); +void RT64ChangeWindow(); +void RT64Shutdown(); void set_rt64_hooks(); diff --git a/lib/RT64-HLE b/lib/RT64-HLE index 12bf0ef..f57eeec 160000 --- a/lib/RT64-HLE +++ b/lib/RT64-HLE @@ -1 +1 @@ -Subproject commit 12bf0efad21fb7cc1f27a630ae6dbcb90ddd1433 +Subproject commit f57eeec44a49cf4fb3ffe6c22d9f0c3d5410fb72 diff --git a/src/recomp/rt64_layer.cpp b/src/recomp/rt64_layer.cpp index 6740c6d..962af8f 100644 --- a/src/recomp/rt64_layer.cpp +++ b/src/recomp/rt64_layer.cpp @@ -2,10 +2,80 @@ #include // #include -#include "../ultramodern/ultramodern.hpp" +#define HLSL_CPU +#include "hle/rt64_application.h" #include "rt64_layer.h" #include "rt64_render_hooks.h" +typedef struct { + // void* hWnd; + // void* hStatusBar; + + // int Reserved; + + unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */ + unsigned char* RDRAM; + unsigned char* DMEM; + unsigned char* IMEM; + + unsigned int* MI_INTR_REG; + + unsigned int* DPC_START_REG; + unsigned int* DPC_END_REG; + unsigned int* DPC_CURRENT_REG; + unsigned int* DPC_STATUS_REG; + unsigned int* DPC_CLOCK_REG; + unsigned int* DPC_BUFBUSY_REG; + unsigned int* DPC_PIPEBUSY_REG; + unsigned int* DPC_TMEM_REG; + + unsigned int* VI_STATUS_REG; + unsigned int* VI_ORIGIN_REG; + unsigned int* VI_WIDTH_REG; + unsigned int* VI_INTR_REG; + unsigned int* VI_V_CURRENT_LINE_REG; + unsigned int* VI_TIMING_REG; + unsigned int* VI_V_SYNC_REG; + unsigned int* VI_H_SYNC_REG; + unsigned int* VI_LEAP_REG; + unsigned int* VI_H_START_REG; + unsigned int* VI_V_START_REG; + unsigned int* VI_V_BURST_REG; + unsigned int* VI_X_SCALE_REG; + unsigned int* VI_Y_SCALE_REG; + + void (*CheckInterrupts)(void); + + unsigned int version; + unsigned int* SP_STATUS_REG; + const unsigned int* RDRAM_SIZE; +} GFX_INFO; + +#ifdef _WIN32 +#define DLLEXPORT extern "C" __declspec(dllexport) +#define DLLIMPORT extern "C" __declspec(dllimport) +#define CALL __cdecl +#else +#define DLLEXPORT extern "C" __attribute__((visibility("default"))) +#define DLLIMPORT extern "C" +#endif + +#if defined(_WIN32) +extern "C" RT64::Application* InitiateGFXWindows(GFX_INFO Gfx_Info, HWND hwnd, DWORD threadId); +#elif defined(__ANDROID__) +static_assert(false && "Unimplemented"); +#elif defined(__linux__) +extern "C" RT64::Application* InitiateGFXLinux(GFX_INFO Gfx_Info, Window window, Display *display); +#else +static_assert(false && "Unimplemented"); +#endif + +DLLIMPORT void ProcessRDPList(void); +DLLIMPORT void ProcessDList(void); +DLLIMPORT void UpdateScreen(void); +DLLIMPORT void ChangeWindow(void); +DLLIMPORT void PluginShutdown(void); + static uint8_t DMEM[0x1000]; static uint8_t IMEM[0x1000]; @@ -45,7 +115,7 @@ void dummy_check_interrupts() { } -bool RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle) { +RT64::Application* RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle) { set_rt64_hooks(); // Dynamic loading //auto RT64 = LoadLibrary("RT64.dll"); @@ -134,3 +204,34 @@ void RT64ChangeWindow() { void RT64Shutdown() { PluginShutdown(); } + +void RT64UpdateConfig(RT64::Application* application, const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config) { + if (new_config.wm_option != old_config.wm_option) { + application->setFullScreen(new_config.wm_option == ultramodern::WindowMode::Fullscreen); + } + + switch (new_config.res_option) { + default: + case ultramodern::Resolution::Auto: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale; + break; + case ultramodern::Resolution::Original: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Original; + break; + case ultramodern::Resolution::Original2x: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; + application->userConfig.resolutionMultiplier = 2.0; + break; + } + + application->userConfig.aspectRatio = new_config.ar_option; + application->userConfig.antialiasing = new_config.msaa_option; + application->userConfig.refreshRate = new_config.rr_option; + application->userConfig.refreshRateTarget = new_config.rr_manual_value; + + application->updateUserConfig(true); + + if (new_config.msaa_option != old_config.msaa_option) { + application->updateMultisampling(); + } +} diff --git a/src/ui/ui_events.cpp b/src/ui/ui_events.cpp index 0152055..7c4f10e 100644 --- a/src/ui/ui_events.cpp +++ b/src/ui/ui_events.cpp @@ -2,6 +2,8 @@ #include "recomp_ui.h" #include "../../ultramodern/ultramodern.hpp" +#include "../../ultramodern/config.hpp" +#include "common/rt64_user_configuration.h" #include "nfd.h" #include "RmlUi/Core.h" @@ -36,15 +38,97 @@ public: } }; +NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, { + {ultramodern::Resolution::Original, "Original"}, + {ultramodern::Resolution::Original2x, "Original2x"}, + {ultramodern::Resolution::Auto, "Auto"}, +}); + +NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, { + {ultramodern::WindowMode::Windowed, "Windowed"}, + {ultramodern::WindowMode::Fullscreen, "Fullscreen"} +}); + +ultramodern::GraphicsConfig cur_options; +ultramodern::GraphicsConfig new_options; +Rml::DataModelHandle options_handle; + +template +void get_option(const T& input, Rml::Variant& output) { + std::string value = ""; + to_json(value, input); + + if (value.empty()) { + throw std::runtime_error("Invalid value :" + std::to_string(int(input))); + } + + output = value; +} + +template +void set_option(T& output, const Rml::Variant& input) { + T value = T::OptionCount; + from_json(input.Get(), value); + + if (value == T::OptionCount) { + throw std::runtime_error("Invalid value :" + input.Get()); + } + + output = value; +} + +template +void bind_option(Rml::DataModelConstructor& constructor, const std::string& name, T* option) { + constructor.BindFunc(name, + [option](Rml::Variant& out) { get_option(*option, out); }, + [option](const Rml::Variant& in) { set_option(*option, in); options_handle.DirtyVariable("options_changed"); } + ); +}; + +void recomp::make_ui_bindings(Rml::Context* context) { + Rml::DataModelConstructor constructor = context->CreateDataModel("graphics_model"); + if (!constructor) { + throw std::runtime_error("Failed to make RmlUi data model constructor"); + } + + bind_option(constructor, "res_option", &new_options.res_option); + bind_option(constructor, "wm_option", &new_options.wm_option); + bind_option(constructor, "ar_option", &new_options.ar_option); + bind_option(constructor, "msaa_option", &new_options.msaa_option); + bind_option(constructor, "rr_option", &new_options.rr_option); + constructor.BindFunc("rr_manual_value", + [](Rml::Variant& out) { + out = new_options.rr_manual_value; + }, + [](const Rml::Variant& in) { + new_options.rr_manual_value = in.Get(); + options_handle.DirtyVariable("options_changed"); + }); + + constructor.BindFunc("options_changed", + [](Rml::Variant& out) { + out = (cur_options != new_options); + }); + + options_handle = constructor.GetModelHandle(); +} + std::unique_ptr recomp::make_event_listener_instancer() { std::unique_ptr ret = std::make_unique(); ret->register_event("start_game", [](Rml::Event& event) { ultramodern::start_game(0); - set_current_menu(Menu::None); + set_current_menu(Menu::Config); } ); + ret->register_event("apply_options", + [](Rml::Event& event) { + cur_options = new_options; + options_handle.DirtyVariable("options_changed"); + update_graphics_config(new_options); + }); + return ret; } diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index e0cc38a..ecf3ed4 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -565,7 +565,7 @@ public: }; bool can_focus(Rml::Element* element) { - return element->GetProperty(Rml::PropertyId::TabIndex)->Get() != Rml::Property(Rml::Style::TabIndex::None); + return element->GetOwnerDocument() != nullptr && element->GetProperty(Rml::PropertyId::TabIndex)->Get() != Rml::Property(Rml::Style::TabIndex::None); } Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element) { @@ -660,7 +660,20 @@ struct { // Revert focus to the previous element if focused on anything without a tab index. // This should prevent the user from losing focus on something that has no navigation. if (focused && !can_focus(focused)) { - prev_focused->Focus(); + // If the previously focused element is still accepting focus, return focus to it. + if (prev_focused && can_focus(prev_focused)) { + prev_focused->Focus(); + } + // Otherwise, check if the currently focused element has a "nav-return" attribute and focus that attribute's value if so. + else { + Rml::Variant* nav_return = focused->GetAttribute("nav-return"); + if (nav_return && nav_return->GetType() == Rml::Variant::STRING) { + Rml::Element* return_element = current_document->GetElementById(nav_return->Get()); + if (return_element) { + return_element->Focus(); + } + } + } } else { prev_focused = current_document->GetFocusLeafNode(); @@ -693,6 +706,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) { SDL_GetWindowSizeInPixels(window, &width, &height); UIContext.rml.context = Rml::CreateContext("main", Rml::Vector2i(width, height)); + recomp::make_ui_bindings(UIContext.rml.context); Rml::Debugger::Initialise(UIContext.rml.context); @@ -730,7 +744,7 @@ bool recomp::try_deque_event(SDL_Event& out) { return ui_event_queue.try_dequeue(out); } -std::atomic open_menu = recomp::Menu::Config;//Launcher; +std::atomic open_menu = recomp::Menu::Launcher; void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_chain_texture) { int num_keys; @@ -778,6 +792,9 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderTexture* swap_ int width, height; SDL_GetWindowSizeInPixels(window, &width, &height); + // Scale the UI based on the window size with 720 vertical resolution as the reference point. + UIContext.rml.context->SetDensityIndependentPixelRatio(height / 720.0f); + UIContext.rml.render_interface->start(command_list, width, height); static int prev_width = 0; diff --git a/ultramodern/config.hpp b/ultramodern/config.hpp new file mode 100644 index 0000000..0f5694a --- /dev/null +++ b/ultramodern/config.hpp @@ -0,0 +1,33 @@ +#ifndef __CONFIG_HPP__ +#define __CONFIG_HPP__ + +#include "common/rt64_user_configuration.h" + +namespace ultramodern { + enum class Resolution { + Original, + Original2x, + Auto, + OptionCount + }; + enum class WindowMode { + Windowed, + Fullscreen, + OptionCount + }; + + struct GraphicsConfig { + Resolution res_option; + WindowMode wm_option; + RT64::UserConfiguration::AspectRatio ar_option; + RT64::UserConfiguration::Antialiasing msaa_option; + RT64::UserConfiguration::RefreshRate rr_option; + int rr_manual_value; + + auto operator<=>(const GraphicsConfig& rhs) const = default; + }; + + void update_graphics_config(const GraphicsConfig& config); +}; + +#endif diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index 7903a92..a926534 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -13,6 +13,8 @@ #include "ultra64.h" #include "ultramodern.hpp" +#include "config.hpp" +#include "rt64_layer.h" #include "recomp.h" #include "recomp_ui.h" #include "rsp.h" @@ -25,7 +27,11 @@ struct SwapBuffersAction { uint32_t origin; }; -using Action = std::variant; +struct UpdateConfigAction { + ultramodern::GraphicsConfig config; +}; + +using Action = std::variant; static struct { struct { @@ -174,12 +180,6 @@ void dp_complete() { osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); } -bool RT64Init(uint8_t* rom, uint8_t* rdram, ultramodern::WindowHandle window_handle); -void RT64SendDL(uint8_t* rdram, const OSTask* task); -void RT64UpdateScreen(uint32_t vi_origin); -void RT64ChangeWindow(); -void RT64Shutdown(); - uint8_t dmem[0x1000]; uint16_t rspReciprocals[512]; uint16_t rspInverseSquareRoots[512]; @@ -257,13 +257,21 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea } } +void ultramodern::update_graphics_config(const ultramodern::GraphicsConfig& config) { + events_context.action_queue.enqueue(UpdateConfigAction{ config }); +} + void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, ultramodern::WindowHandle window_handle) { using namespace std::chrono_literals; ultramodern::set_native_thread_name("Gfx Thread"); ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal); - if (!RT64Init(rom, rdram, window_handle)) { + ultramodern::GraphicsConfig cur_config{}; + + RT64::Application* application = RT64Init(rom, rdram, window_handle); + + if (application == nullptr) { throw std::runtime_error("Failed to initialize RT64!"); } @@ -286,10 +294,17 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read sp_complete(); RT64SendDL(rdram, &task_action->task); dp_complete(); - } else if (const auto* swap_action = std::get_if(&action)) { + } + else if (const auto* swap_action = std::get_if(&action)) { events_context.vi.current_buffer = events_context.vi.next_buffer; RT64UpdateScreen(swap_action->origin); } + else if (const auto* config_action = std::get_if(&action)) { + if (cur_config != config_action->config) { + RT64UpdateConfig(application, cur_config, config_action->config); + cur_config = config_action->config; + } + } } } recomp::destroy_ui(); diff --git a/ultramodern/ultra64.h b/ultramodern/ultra64.h index 4ee53f3..a056031 100644 --- a/ultramodern/ultra64.h +++ b/ultramodern/ultra64.h @@ -136,10 +136,10 @@ typedef struct { PTR(u64) yield_data_ptr; u32 yield_data_size; -} OSTask_t; +} OSTask_s; typedef union { - OSTask_t t; + OSTask_s t; int64_t force_structure_alignment; } OSTask;