From baa86fcd1b2c3d516faedacb63c83147ee244a84 Mon Sep 17 00:00:00 2001 From: Ben Vanik Date: Sun, 19 Jul 2015 14:43:27 -0700 Subject: [PATCH] Migrating xam UI to elemental-forms. Fixes #345. --- src/xenia/app/emulator_window.cc | 6 +- src/xenia/base/string_buffer.h | 6 +- src/xenia/kernel/xam_ui.cc | 205 ++++++++++++++++------ src/xenia/kernel/xboxkrnl_module.cc | 4 + src/xenia/profiling.cc | 2 +- src/xenia/ui/gl/gl4_elemental_renderer.cc | 20 ++- src/xenia/ui/gl/gl_profiler_display.cc | 30 ++-- third_party/elemental-forms | 2 +- 8 files changed, 195 insertions(+), 80 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index cba05cd6a..0acd6e2ab 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -70,7 +70,7 @@ bool EmulatorWindow::Initialize() { window_->on_key_down.AddListener([this](KeyEvent& e) { bool handled = true; switch (e.key_code()) { - case 0x0D: { // numpad enter + case 0x6A: { // numpad * CpuTimeScalarReset(); } break; case 0x6D: { // numpad minus @@ -94,6 +94,8 @@ bool EmulatorWindow::Initialize() { // Allow users to escape fullscreen (but not enter it). if (window_->is_fullscreen()) { window_->ToggleFullscreen(false); + } else { + handled = false; } } break; @@ -121,7 +123,7 @@ bool EmulatorWindow::Initialize() { auto cpu_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&CPU"); { cpu_menu->AddChild(MenuItem::Create( - MenuItem::Type::kString, L"&Reset Time Scalar", L"Numpad Enter", + MenuItem::Type::kString, L"&Reset Time Scalar", L"Numpad *", std::bind(&EmulatorWindow::CpuTimeScalarReset, this))); cpu_menu->AddChild(MenuItem::Create( MenuItem::Type::kString, L"Time Scalar /= 2", L"Numpad -", diff --git a/src/xenia/base/string_buffer.h b/src/xenia/base/string_buffer.h index f15c9e7c4..35b3d3ff8 100644 --- a/src/xenia/base/string_buffer.h +++ b/src/xenia/base/string_buffer.h @@ -38,9 +38,9 @@ class StringBuffer { private: void Grow(size_t additional_length); - char* buffer_; - size_t buffer_offset_; - size_t buffer_capacity_; + char* buffer_ = nullptr; + size_t buffer_offset_ = 0; + size_t buffer_capacity_ = 0; }; } // namespace xe diff --git a/src/xenia/kernel/xam_ui.cc b/src/xenia/kernel/xam_ui.cc index adc645b8e..654d75019 100644 --- a/src/xenia/kernel/xam_ui.cc +++ b/src/xenia/kernel/xam_ui.cc @@ -7,8 +7,8 @@ ****************************************************************************** */ +#include "el/elemental_forms.h" #include "xenia/base/logging.h" -#include "xenia/base/platform_win.h" #include "xenia/emulator.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" @@ -16,17 +16,87 @@ #include "xenia/ui/window.h" #include "xenia/xbox.h" -#include - namespace xe { namespace kernel { SHIM_CALL XamIsUIActive_shim(PPCContext* ppc_context, KernelState* kernel_state) { XELOGD("XamIsUIActive()"); - SHIM_SET_RETURN_32(0); + SHIM_SET_RETURN_32(el::ModalWindow::is_any_visible()); } +class MessageBoxWindow : public el::ModalWindow { + public: + MessageBoxWindow(xe::threading::Fence* fence) + : ModalWindow([fence]() { fence->Signal(); }) {} + ~MessageBoxWindow() override = default; + + // TODO(benvanik): icon. + void Show(el::Element* root_element, std::wstring title, std::wstring message, + std::vector buttons, uint32_t default_button, + uint32_t* out_chosen_button) { + title_ = std::move(title); + message_ = std::move(message); + buttons_ = std::move(buttons); + default_button_ = default_button; + out_chosen_button_ = out_chosen_button; + + // In case we are cancelled, always set default button. + *out_chosen_button_ = default_button; + + ModalWindow::Show(root_element); + + EnsureFocus(); + } + + protected: + void BuildUI() override { + using namespace el::dsl; + + set_text(xe::to_string(title_)); + + auto button_bar = LayoutBoxNode().distribution_position( + LayoutDistributionPosition::kRightBottom); + for (int32_t i = 0; i < buttons_.size(); ++i) { + button_bar.child(ButtonNode(xe::to_string(buttons_[i]).c_str()) + .id(100 + i) + .data(100 + i)); + } + + LoadNodeTree( + LayoutBoxNode() + .axis(Axis::kY) + .gravity(Gravity::kAll) + .position(LayoutPosition::kLeftTop) + .distribution(LayoutDistribution::kAvailable) + .distribution_position(LayoutDistributionPosition::kLeftTop) + .child(LabelNode(xe::to_string(message_).c_str())) + .child(button_bar)); + + auto default_button = GetElementById(100 + default_button_); + el::Button::set_auto_focus_state(true); + default_button->set_focus(el::FocusReason::kNavigation); + } + + bool OnEvent(const el::Event& ev) override { + if (ev.target->IsOfType() && ev.type == el::EventType::kClick) { + int data_value = ev.target->data.as_integer(); + if (data_value) { + *out_chosen_button_ = data_value - 100; + } + Die(); + return true; + } + return ModalWindow::OnEvent(ev); + } + + std::wstring title_; + std::wstring message_; + std::vector buttons_; + uint32_t default_button_ = 0; + uint32_t* out_chosen_button_ = nullptr; +}; + // http://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/?jdfwkey=sb0vm SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, KernelState* kernel_state) { @@ -66,49 +136,29 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, // Auto-pick the focused button. chosen_button = active_button; } else { - TASKDIALOGCONFIG config = {0}; - config.cbSize = sizeof(config); - config.hInstance = GetModuleHandle(nullptr); - config.hwndParent = - HWND(kernel_state->emulator()->display_window()->native_handle()); - config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | // esc to exit - TDF_POSITION_RELATIVE_TO_WINDOW; // center in window - config.dwCommonButtons = 0; - config.pszWindowTitle = title.c_str(); - switch (flags & 0xF) { - case 0: - config.pszMainIcon = nullptr; - break; - case 1: - config.pszMainIcon = TD_ERROR_ICON; - break; - case 2: - config.pszMainIcon = TD_WARNING_ICON; - break; - case 3: - config.pszMainIcon = TD_INFORMATION_ICON; - break; - } - config.pszMainInstruction = text.c_str(); - config.pszContent = nullptr; - std::vector taskdialog_buttons; - for (uint32_t i = 0; i < button_count; ++i) { - taskdialog_buttons.push_back({1000 + int(i), buttons[i].c_str()}); - } - config.pButtons = taskdialog_buttons.data(); - config.cButtons = button_count; - config.nDefaultButton = active_button; - int button_pressed = 0; - TaskDialogIndirect(&config, &button_pressed, nullptr, nullptr); - switch (button_pressed) { - default: - chosen_button = button_pressed - 1000; - break; - case IDCANCEL: - // User cancelled, just pick default. - chosen_button = active_button; - break; - } + auto display_window = kernel_state->emulator()->display_window(); + xe::threading::Fence fence; + display_window->loop()->PostSynchronous([&]() { + auto root_element = display_window->root_element(); + auto window = new MessageBoxWindow(&fence); + switch (flags & 0xF) { + case 0: + // config.pszMainIcon = nullptr; + break; + case 1: + // config.pszMainIcon = TD_ERROR_ICON; + break; + case 2: + // config.pszMainIcon = TD_WARNING_ICON; + break; + case 3: + // config.pszMainIcon = TD_INFORMATION_ICON; + break; + } + window->Show(root_element, title, text, buttons, active_button, + &chosen_button); + }); + fence.Wait(); } SHIM_SET_MEM_32(result_ptr, chosen_button); @@ -116,20 +166,71 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); } +class DirtyDiscWindow : public el::ModalWindow { + public: + DirtyDiscWindow(xe::threading::Fence* fence) + : ModalWindow([fence]() { fence->Signal(); }) {} + ~DirtyDiscWindow() override = default; + + protected: + void BuildUI() override { + using namespace el::dsl; + + set_text("Disc Read Error"); + + LoadNodeTree( + LayoutBoxNode() + .axis(Axis::kY) + .gravity(Gravity::kAll) + .position(LayoutPosition::kLeftTop) + .distribution(LayoutDistribution::kAvailable) + .distribution_position(LayoutDistributionPosition::kLeftTop) + .child(LabelNode( + "There's been an issue reading content from the game disc.")) + .child(LabelNode( + "This is likely caused by bad or unimplemented file IO " + "calls.")) + .child(LayoutBoxNode() + .distribution_position( + LayoutDistributionPosition::kRightBottom) + .child(ButtonNode("Exit").id("exit_button")))); + } + + bool OnEvent(const el::Event& ev) override { + if (ev.target->id() == TBIDC("exit_button") && + ev.type == el::EventType::kClick) { + Die(); + return true; + } + return ModalWindow::OnEvent(ev); + } +}; + SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context, KernelState* kernel_state) { uint32_t user_index = SHIM_GET_ARG_32(0); XELOGD("XamShowDirtyDiscErrorUI(%d)", user_index); - int button_pressed = 0; - TaskDialog(HWND(kernel_state->emulator()->display_window()->native_handle()), - GetModuleHandle(nullptr), L"Disc Read Error", - L"Game is claiming to be unable to read game data!", nullptr, - TDCBF_CLOSE_BUTTON, TD_ERROR_ICON, &button_pressed); + if (FLAGS_headless) { + assert_always(); + exit(1); + return; + } + + auto display_window = kernel_state->emulator()->display_window(); + xe::threading::Fence fence; + display_window->loop()->PostSynchronous([&]() { + auto root_element = display_window->root_element(); + auto window = new DirtyDiscWindow(&fence); + window->Show(root_element); + }); + fence.Wait(); // This is death, and should never return. + // TODO(benvanik): cleaner exit. assert_always(); + exit(1); } } // namespace kernel diff --git a/src/xenia/kernel/xboxkrnl_module.cc b/src/xenia/kernel/xboxkrnl_module.cc index 08b5153c9..b44ba686f 100644 --- a/src/xenia/kernel/xboxkrnl_module.cc +++ b/src/xenia/kernel/xboxkrnl_module.cc @@ -171,6 +171,10 @@ int XboxkrnlModule::LaunchModule(const char* path) { // Create and register the module. We keep it local to this function and // dispose it on exit. auto module = kernel_state_->LoadUserModule(path); + if (!module) { + XELOGE("Failed to load user module %s.", path); + return 2; + } // Set as the main module, while running. kernel_state_->SetExecutableModule(module); diff --git a/src/xenia/profiling.cc b/src/xenia/profiling.cc index f73c8a4ac..468170bef 100644 --- a/src/xenia/profiling.cc +++ b/src/xenia/profiling.cc @@ -35,7 +35,7 @@ std::unique_ptr Profiler::display_ = nullptr; bool Profiler::is_enabled() { return true; } -bool Profiler::is_visible() { return MicroProfileIsDrawing(); } +bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); } void Profiler::Initialize() { // Custom groups. diff --git a/src/xenia/ui/gl/gl4_elemental_renderer.cc b/src/xenia/ui/gl/gl4_elemental_renderer.cc index 551010943..431e53a21 100644 --- a/src/xenia/ui/gl/gl4_elemental_renderer.cc +++ b/src/xenia/ui/gl/gl4_elemental_renderer.cc @@ -223,6 +223,7 @@ void GL4ElementalRenderer::BeginPaint(int render_target_w, glUseProgram(program_); glBindVertexArray(vao_); + glProgramUniform1f(program_, 2, 0.0f); } void GL4ElementalRenderer::EndPaint() { @@ -250,15 +251,6 @@ void GL4ElementalRenderer::Flush() { } void GL4ElementalRenderer::RenderBatch(Batch* batch) { - auto bitmap = static_cast(batch->bitmap); - if (bitmap != current_bitmap_) { - current_bitmap_ = bitmap; - Flush(); - glProgramUniformHandleui64ARB(program_, 1, - bitmap ? bitmap->gpu_handle_ : 0); - glProgramUniform1f(program_, 2, bitmap ? 1.0f : 0.0f); - } - if (draw_command_count_ + 1 > kMaxCommands) { Flush(); } @@ -266,6 +258,16 @@ void GL4ElementalRenderer::RenderBatch(Batch* batch) { if (!vertex_buffer_.CanAcquire(total_length)) { Flush(); } + + auto bitmap = static_cast(batch->bitmap); + if (bitmap != current_bitmap_) { + Flush(); + current_bitmap_ = bitmap; + glProgramUniformHandleui64ARB(program_, 1, + bitmap ? bitmap->gpu_handle_ : 0); + glProgramUniform1f(program_, 2, bitmap ? 1.0f : 0.0f); + } + auto allocation = vertex_buffer_.Acquire(total_length); // TODO(benvanik): custom batcher that lets us use the ringbuffer memory diff --git a/src/xenia/ui/gl/gl_profiler_display.cc b/src/xenia/ui/gl/gl_profiler_display.cc index 5a3f070ff..d88d23156 100644 --- a/src/xenia/ui/gl/gl_profiler_display.cc +++ b/src/xenia/ui/gl/gl_profiler_display.cc @@ -144,43 +144,49 @@ GLProfilerDisplay::GLProfilerDisplay(xe::ui::Window* window) window_->on_painted.AddListener([this](UIEvent& e) { Profiler::Present(); }); // Pass through mouse events. - window_->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) { - if (Profiler::is_enabled()) { + window_->on_mouse_down.AddListener([this](xe::ui::MouseEvent& e) { + if (Profiler::is_visible()) { Profiler::OnMouseDown(e.button() == xe::ui::MouseEvent::Button::kLeft, e.button() == xe::ui::MouseEvent::Button::kRight); e.set_handled(true); + window_->Invalidate(); } }); - window_->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) { - if (Profiler::is_enabled()) { + window_->on_mouse_up.AddListener([this](xe::ui::MouseEvent& e) { + if (Profiler::is_visible()) { Profiler::OnMouseUp(); e.set_handled(true); + window_->Invalidate(); } }); - window_->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) { - if (Profiler::is_enabled()) { + window_->on_mouse_move.AddListener([this](xe::ui::MouseEvent& e) { + if (Profiler::is_visible()) { Profiler::OnMouseMove(e.x(), e.y()); e.set_handled(true); + window_->Invalidate(); } }); - window_->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) { - if (Profiler::is_enabled()) { + window_->on_mouse_wheel.AddListener([this](xe::ui::MouseEvent& e) { + if (Profiler::is_visible()) { Profiler::OnMouseWheel(e.x(), e.y(), -e.dy()); e.set_handled(true); + window_->Invalidate(); } }); // Watch for toggle/mode keys and such. - window_->on_key_down.AddListener([](xe::ui::KeyEvent& e) { - if (Profiler::is_enabled()) { + window_->on_key_down.AddListener([this](xe::ui::KeyEvent& e) { + if (Profiler::is_visible()) { Profiler::OnKeyDown(e.key_code()); e.set_handled(true); + window_->Invalidate(); } }); - window_->on_key_up.AddListener([](xe::ui::KeyEvent& e) { - if (Profiler::is_enabled()) { + window_->on_key_up.AddListener([this](xe::ui::KeyEvent& e) { + if (Profiler::is_visible()) { Profiler::OnKeyUp(e.key_code()); e.set_handled(true); + window_->Invalidate(); } }); } diff --git a/third_party/elemental-forms b/third_party/elemental-forms index 187fb988e..b0ecdb343 160000 --- a/third_party/elemental-forms +++ b/third_party/elemental-forms @@ -1 +1 @@ -Subproject commit 187fb988e015576f840d47f6722b094d1d8cd7e8 +Subproject commit b0ecdb343c06c157c44acbcc8d140c88ab1568f3