diff --git a/libxenia.vcxproj b/libxenia.vcxproj index e591b3cee..8ba588138 100644 --- a/libxenia.vcxproj +++ b/libxenia.vcxproj @@ -165,22 +165,18 @@ - - + - - - + + + + - - - - - + @@ -396,8 +392,6 @@ - - @@ -405,19 +399,13 @@ - - + - + - - - - - - + diff --git a/libxenia.vcxproj.filters b/libxenia.vcxproj.filters index bfb38c16a..314a1eb63 100644 --- a/libxenia.vcxproj.filters +++ b/libxenia.vcxproj.filters @@ -68,9 +68,6 @@ {42d47a43-1af4-4e1a-9ed7-afa7f7d18e9f} - - {268545c9-fbdf-46d2-96f6-35188cec09d6} - {c1ac0db1-2f4b-4376-b1dc-e6355c99b395} @@ -159,7 +156,7 @@ {82795389-e855-4cd6-a3b6-9580030cebf2} - {c38dacd1-1e4c-4cd1-847e-19b394e8313d} + {f598eed5-11dc-4ef8-a7d4-28ec5e43009b} @@ -409,24 +406,6 @@ src\xenia\hid\xinput - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui - - - src\xenia\ui - src\xenia\ui @@ -655,9 +634,6 @@ src\xenia\apu - - src\xenia\ui\win32 - src\xenia\hid @@ -715,30 +691,39 @@ src\xenia\vfs + + src\xenia\ui + + + src\xenia\ui + + + src\xenia\ui + + + src\xenia\ui + src\xenia\ui\gl - - src\xenia\ui\gl - - - src\xenia\ui\gl - - - src\xenia\ui\gl - src\xenia\ui\gl - - src\xenia\ui + + src\xenia\ui\gl - + src\xenia\ui\gl src\xenia\ui\gl + + src\xenia\ui + + + src\xenia\ui + @@ -1008,27 +993,9 @@ src\xenia\hid\xinput - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui\win32 - - - src\xenia\ui - src\xenia\ui - - src\xenia\ui - src\xenia\ui @@ -1296,9 +1263,6 @@ src\xenia\ui - - src\xenia\ui\win32 - src\xenia\hid @@ -1362,36 +1326,33 @@ src\xenia\cpu\backend\x64 + + src\xenia\ui + + + src\xenia\ui + src\xenia\ui\gl - - src\xenia\ui\gl - - - src\xenia\ui\gl - - - src\xenia\ui\gl - src\xenia\ui\gl - - src\xenia\ui - src\xenia\ui\gl - - src\xenia\ui + + src\xenia\ui\gl - + src\xenia\ui\gl src\xenia\ui\gl + + src\xenia\ui + diff --git a/src/xenia/debug/ui/application.cc b/src/xenia/debug/ui/application.cc index 62bf87e17..15ccf21aa 100644 --- a/src/xenia/debug/ui/application.cc +++ b/src/xenia/debug/ui/application.cc @@ -27,7 +27,10 @@ Application* Application::current() { return current_application_; } -Application::Application() { current_application_ = this; } +Application::Application() { + current_application_ = this; + loop_ = xe::ui::Loop::Create(); +} Application::~Application() { assert_true(current_application_ == this); @@ -65,7 +68,7 @@ bool Application::Initialize() { } void Application::Quit() { - loop_.Quit(); + loop_->Quit(); // TODO(benvanik): proper exit. XELOGI("User-initiated death!"); diff --git a/src/xenia/debug/ui/application.h b/src/xenia/debug/ui/application.h index 6ff3cd70b..72b4a3634 100644 --- a/src/xenia/debug/ui/application.h +++ b/src/xenia/debug/ui/application.h @@ -12,7 +12,7 @@ #include -#include "xenia/ui/platform.h" +#include "xenia/ui/loop.h" namespace xe { namespace debug { @@ -27,7 +27,7 @@ class Application { static std::unique_ptr Create(); static Application* current(); - xe::ui::Loop* loop() { return &loop_; } + xe::ui::Loop* loop() { return loop_.get(); } MainWindow* main_window() const { return main_window_.get(); } void Quit(); @@ -37,7 +37,7 @@ class Application { bool Initialize(); - xe::ui::PlatformLoop loop_; + std::unique_ptr loop_; std::unique_ptr main_window_; }; diff --git a/src/xenia/debug/ui/main_window.cc b/src/xenia/debug/ui/main_window.cc index 85e571f2a..3177f9f94 100644 --- a/src/xenia/debug/ui/main_window.cc +++ b/src/xenia/debug/ui/main_window.cc @@ -15,51 +15,46 @@ #include "xenia/base/logging.h" #include "xenia/base/platform.h" #include "xenia/base/threading.h" - -// TODO(benvanik): platform based. -#include "xenia/ui/gl/wgl_elemental_control.h" +#include "xenia/ui/gl/gl_context.h" namespace xe { namespace debug { namespace ui { using xe::ui::MenuItem; -using xe::ui::PlatformMenu; -using xe::ui::PlatformWindow; - -enum Commands { - IDC_FILE_EXIT, - - IDC_HELP_WEBSITE, - IDC_HELP_ABOUT, -}; const std::wstring kBaseTitle = L"xenia debugger"; -MainWindow::MainWindow(Application* app) - : PlatformWindow(app->loop(), kBaseTitle), - app_(app), - main_menu_(MenuItem::Type::kNormal) {} +MainWindow::MainWindow(Application* app) : app_(app) {} +// main_menu_(MenuItem::Type::kNormal) {} MainWindow::~MainWindow() = default; bool MainWindow::Initialize() { - if (!PlatformWindow::Initialize()) { + platform_window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle); + if (!platform_window_) { return false; } + platform_window_->Initialize(); + platform_window_->set_context( + xe::ui::gl::GLContext::Create(platform_window_.get())); + platform_window_->MakeReady(); - on_key_down.AddListener([this](xe::ui::KeyEvent& e) { + platform_window_->on_closed.AddListener( + std::bind(&MainWindow::OnClose, this)); + + platform_window_->on_key_down.AddListener([this](xe::ui::KeyEvent& e) { bool handled = true; switch (e.key_code()) { case 0x1B: { // VK_ESCAPE // Allow users to escape fullscreen (but not enter it). - if (is_fullscreen()) { - ToggleFullscreen(false); + if (platform_window_->is_fullscreen()) { + platform_window_->ToggleFullscreen(false); } } break; case 0x70: { // VK_F1 - OnCommand(Commands::IDC_HELP_WEBSITE); + LaunchBrowser("http://xenia.jp"); } break; default: { handled = false; } break; @@ -68,35 +63,30 @@ bool MainWindow::Initialize() { }); // Main menu. - auto file_menu = - std::make_unique(MenuItem::Type::kPopup, L"&File"); + auto main_menu = MenuItem::Create(MenuItem::Type::kNormal); + auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&File"); { - file_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_FILE_EXIT, L"E&xit", L"Alt+F4")); + file_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"E&xit", L"Alt+F4", + [this]() { platform_window_->Close(); })); } - main_menu_.AddChild(std::move(file_menu)); + main_menu->AddChild(std::move(file_menu)); // Help menu. - auto help_menu = - std::make_unique(MenuItem::Type::kPopup, L"&Help"); + auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Help"); { - help_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_HELP_WEBSITE, L"&Website...", - L"F1")); - help_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_HELP_ABOUT, L"&About...")); + help_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&Website...", L"F1", + []() { LaunchBrowser("http://xenia.jp"); })); + help_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&About...", + []() { LaunchBrowser("http://xenia.jp/about/"); })); } - main_menu_.AddChild(std::move(help_menu)); + main_menu->AddChild(std::move(help_menu)); - set_menu(&main_menu_); + platform_window_->set_main_menu(std::move(main_menu)); - // Setup the GL control that actually does the drawing. - // We run here in the loop and only touch it (and its context) on this - // thread. That means some sync-fu when we want to swap. - control_ = std::make_unique(loop()); - AddChild(control_.get()); - - Resize(1440, 1200); + platform_window_->Resize(1440, 1200); BuildUI(); @@ -107,7 +97,7 @@ void MainWindow::BuildUI() { using namespace el::dsl; el::AnimationBlocker animation_blocker; - auto root_element = control_->root_element(); + auto root_element = platform_window_->root_element(); window_ = std::make_unique(); window_->set_settings(el::WindowSettings::kFullScreen); root_element->AddChild(window_.get()); @@ -154,21 +144,6 @@ void MainWindow::BuildUI() { void MainWindow::OnClose() { app_->Quit(); } -void MainWindow::OnCommand(int id) { - switch (id) { - case IDC_FILE_EXIT: { - Close(); - } break; - - case IDC_HELP_WEBSITE: { - LaunchBrowser("http://xenia.jp"); - } break; - case IDC_HELP_ABOUT: { - LaunchBrowser("http://xenia.jp/about/"); - } break; - } -} - } // namespace ui } // namespace debug } // namespace xe diff --git a/src/xenia/debug/ui/main_window.h b/src/xenia/debug/ui/main_window.h index 5c17c168d..c51117a42 100644 --- a/src/xenia/debug/ui/main_window.h +++ b/src/xenia/debug/ui/main_window.h @@ -16,18 +16,16 @@ #include "xenia/debug/ui/application.h" #include "xenia/debug/ui/views/cpu/cpu_view.h" #include "xenia/debug/ui/views/gpu/gpu_view.h" -#include "xenia/ui/elemental_control.h" -#include "xenia/ui/platform.h" #include "xenia/ui/window.h" namespace xe { namespace debug { namespace ui { -class MainWindow : public xe::ui::PlatformWindow { +class MainWindow { public: MainWindow(Application* app); - ~MainWindow() override; + ~MainWindow(); Application* app() const { return app_; } @@ -38,13 +36,11 @@ class MainWindow : public xe::ui::PlatformWindow { private: void BuildUI(); - void OnClose() override; - void OnCommand(int id) override; + void OnClose(); Application* app_ = nullptr; - xe::ui::PlatformMenu main_menu_; - std::unique_ptr control_; + std::unique_ptr platform_window_; std::unique_ptr window_; struct { el::SplitContainer* split_container; diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 2ce75b66e..b643c98a4 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -23,7 +23,6 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/modules.h" #include "xenia/memory.h" -#include "xenia/ui/main_window.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/vfs/devices/disc_image_device.h" #include "xenia/vfs/devices/host_path_device.h" @@ -68,14 +67,13 @@ Emulator::~Emulator() { debugger_.reset(); export_resolver_.reset(); - - // Kill the window last, as until the graphics system/etc is dead it's needed. - display_window_.reset(); } -X_STATUS Emulator::Setup() { +X_STATUS Emulator::Setup(ui::Window* display_window) { X_STATUS result = X_STATUS_UNSUCCESSFUL; + display_window_ = display_window; + // Initialize clock. // 360 uses a 50MHz clock. Clock::set_guest_tick_frequency(50000000); @@ -93,9 +91,6 @@ X_STATUS Emulator::Setup() { &system_affinity_mask); SetProcessAffinityMask(process_handle, system_affinity_mask); - // Create the main window. Other parts will hook into this. - display_window_ = ui::MainWindow::Create(this); - // Create memory system first, as it is required for other systems. memory_ = std::make_unique(); result = memory_->Initialize(); @@ -129,6 +124,10 @@ X_STATUS Emulator::Setup() { if (!graphics_system_) { return X_STATUS_NOT_IMPLEMENTED; } + display_window_->loop()->PostSynchronous([this]() { + display_window_->set_context( + graphics_system_->CreateContext(display_window_)); + }); // Initialize the HID. input_system_ = std::move(xe::hid::InputSystem::Create(this)); @@ -152,7 +151,7 @@ X_STATUS Emulator::Setup() { return result; } result = graphics_system_->Setup(processor_.get(), display_window_->loop(), - display_window_.get()); + display_window_); if (result) { return result; } @@ -171,6 +170,17 @@ X_STATUS Emulator::Setup() { kernel_state_->LoadKernelModule(); kernel_state_->LoadKernelModule(); + // Finish initializing the display. + display_window_->loop()->PostSynchronous([this]() { + { + xe::ui::GraphicsContextLock context_lock(display_window_->context()); + auto profiler_display = + display_window_->context()->CreateProfilerDisplay(); + Profiler::set_display(std::move(profiler_display)); + } + display_window_->MakeReady(); + }); + return result; } diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 93b39ae73..44d6e19f1 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -15,7 +15,7 @@ #include "xenia/debug/debugger.h" #include "xenia/kernel/kernel_state.h" #include "xenia/memory.h" -#include "xenia/ui/platform.h" +#include "xenia/ui/window.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/xbox.h" @@ -35,9 +35,6 @@ class GraphicsSystem; namespace hid { class InputSystem; } // namespace hid -namespace ui { -class MainWindow; -} // namespace ui } // namespace xe namespace xe { @@ -49,7 +46,7 @@ class Emulator { const std::wstring& command_line() const { return command_line_; } - ui::PlatformWindow* display_window() const { return display_window_.get(); } + ui::Window* display_window() const { return display_window_; } Memory* memory() const { return memory_.get(); } @@ -70,7 +67,7 @@ class Emulator { kernel::KernelState* kernel_state() const { return kernel_state_.get(); } - X_STATUS Setup(); + X_STATUS Setup(ui::Window* display_window); X_STATUS LaunchPath(std::wstring path); X_STATUS LaunchXexFile(std::wstring path); @@ -83,7 +80,7 @@ class Emulator { std::wstring command_line_; - std::unique_ptr display_window_; + ui::Window* display_window_; std::unique_ptr memory_; diff --git a/src/xenia/emulator_window.cc b/src/xenia/emulator_window.cc new file mode 100644 index 000000000..37853981c --- /dev/null +++ b/src/xenia/emulator_window.cc @@ -0,0 +1,225 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/emulator_window.h" + +#include "xenia/base/clock.h" +#include "xenia/base/logging.h" +#include "xenia/base/platform.h" +#include "xenia/base/threading.h" +#include "xenia/gpu/graphics_system.h" +#include "xenia/emulator.h" +#include "xenia/profiling.h" + +namespace xe { + +using xe::ui::KeyEvent; +using xe::ui::MenuItem; +using xe::ui::MouseEvent; +using xe::ui::UIEvent; + +const std::wstring kBaseTitle = L"xenia"; + +EmulatorWindow::EmulatorWindow(Emulator* emulator) + : emulator_(emulator), + loop_(ui::Loop::Create()), + window_(ui::Window::Create(loop_.get(), kBaseTitle)) {} + +EmulatorWindow::~EmulatorWindow() = default; + +std::unique_ptr EmulatorWindow::Create(Emulator* emulator) { + std::unique_ptr emulator_window(new EmulatorWindow(emulator)); + + emulator_window->loop()->PostSynchronous([&emulator_window]() { + xe::threading::set_name("Win32 Loop"); + xe::Profiler::ThreadEnter("Win32 Loop"); + + if (!emulator_window->Initialize()) { + XEFATAL("Failed to initialize main window"); + exit(1); + } + }); + + return emulator_window; +} + +bool EmulatorWindow::Initialize() { + if (!window_->Initialize()) { + XELOGE("Failed to initialize platform window"); + return false; + } + + UpdateTitle(); + + window_->on_closed.AddListener([this](UIEvent& e) { + loop_->Quit(); + + // TODO(benvanik): proper exit. + XELOGI("User-initiated death!"); + exit(1); + }); + loop_->on_quit.AddListener([this](UIEvent& e) { window_.reset(); }); + + window_->on_key_down.AddListener([this](KeyEvent& e) { + bool handled = true; + switch (e.key_code()) { + case 0x0D: { // numpad enter + CpuTimeScalarReset(); + } break; + case 0x6D: { // numpad minus + CpuTimeScalarSetHalf(); + } break; + case 0x6B: { // numpad plus + CpuTimeScalarSetDouble(); + } break; + + case 0x73: { // VK_F4 + GpuTraceFrame(); + } break; + case 0x74: { // VK_F5 + GpuClearCaches(); + } break; + + case 0x7A: { // VK_F11 + ToggleFullscreen(); + } break; + case 0x1B: { // VK_ESCAPE + // Allow users to escape fullscreen (but not enter it). + if (window_->is_fullscreen()) { + window_->ToggleFullscreen(false); + } + } break; + + case 0x70: { // VK_F1 + ShowHelpWebsite(); + } break; + + default: { handled = false; } break; + } + e.set_handled(handled); + }); + + // Main menu. + // FIXME: This code is really messy. + auto main_menu = MenuItem::Create(MenuItem::Type::kNormal); + auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&File"); + { + file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, L"E&xit", + L"Alt+F4", + [this]() { window_->Close(); })); + } + main_menu->AddChild(std::move(file_menu)); + + // CPU menu. + 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", + std::bind(&EmulatorWindow::CpuTimeScalarReset, this))); + cpu_menu->AddChild(MenuItem::Create( + MenuItem::Type::kString, L"Time Scalar /= 2", L"Numpad -", + std::bind(&EmulatorWindow::CpuTimeScalarSetHalf, this))); + cpu_menu->AddChild(MenuItem::Create( + MenuItem::Type::kString, L"Time Scalar *= 2", L"Numpad +", + std::bind(&EmulatorWindow::CpuTimeScalarSetDouble, this))); + } + cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator)); + { + cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, + L"Toggle Profiler &Display", L"Tab", + []() { Profiler::ToggleDisplay(); })); + cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, + L"&Pause/Resume Profiler", L"`", + []() { Profiler::TogglePause(); })); + } + main_menu->AddChild(std::move(cpu_menu)); + + // GPU menu. + auto gpu_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&GPU"); + { + gpu_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&Trace Frame", L"F4", + std::bind(&EmulatorWindow::GpuTraceFrame, this))); + } + gpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator)); + { + gpu_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&Clear Caches", L"F5", + std::bind(&EmulatorWindow::GpuClearCaches, this))); + } + main_menu->AddChild(std::move(gpu_menu)); + + // Window menu. + auto window_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Window"); + { + window_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&Fullscreen", L"F11", + std::bind(&EmulatorWindow::ToggleFullscreen, this))); + } + main_menu->AddChild(std::move(window_menu)); + + // Help menu. + auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Help"); + { + help_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, L"&Website...", L"F1", + std::bind(&EmulatorWindow::ShowHelpWebsite, this))); + help_menu->AddChild(MenuItem::Create( + MenuItem::Type::kString, L"&About...", + [this]() { LaunchBrowser("http://xenia.jp/about/"); })); + } + main_menu->AddChild(std::move(help_menu)); + + window_->set_main_menu(std::move(main_menu)); + + window_->Resize(1280, 720); + + return true; +} + +void EmulatorWindow::CpuTimeScalarReset() { + Clock::set_guest_time_scalar(1.0); + UpdateTitle(); +} + +void EmulatorWindow::CpuTimeScalarSetHalf() { + Clock::set_guest_time_scalar(Clock::guest_time_scalar() / 2.0); + UpdateTitle(); +} + +void EmulatorWindow::CpuTimeScalarSetDouble() { + Clock::set_guest_time_scalar(Clock::guest_time_scalar() * 2.0); + UpdateTitle(); +} + +void EmulatorWindow::GpuTraceFrame() { + emulator()->graphics_system()->RequestFrameTrace(); +} + +void EmulatorWindow::GpuClearCaches() { + emulator()->graphics_system()->ClearCaches(); +} + +void EmulatorWindow::ToggleFullscreen() { + window_->ToggleFullscreen(!window_->is_fullscreen()); +} + +void EmulatorWindow::ShowHelpWebsite() { LaunchBrowser("http://xenia.jp"); } + +void EmulatorWindow::UpdateTitle() { + std::wstring title(kBaseTitle); + if (Clock::guest_time_scalar() != 1.0) { + title += L" (@"; + title += xe::to_wstring(std::to_string(Clock::guest_time_scalar())); + title += L"x)"; + } + window_->set_title(title); +} + +} // namespace xe diff --git a/src/xenia/emulator_window.h b/src/xenia/emulator_window.h new file mode 100644 index 000000000..4db17764a --- /dev/null +++ b/src/xenia/emulator_window.h @@ -0,0 +1,55 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_EMULATOR_WINDOW_H_ +#define XENIA_EMULATOR_WINDOW_H_ + +#include + +#include "xenia/ui/loop.h" +#include "xenia/ui/menu_item.h" +#include "xenia/ui/window.h" +#include "xenia/xbox.h" + +namespace xe { + +class Emulator; + +class EmulatorWindow { + public: + virtual ~EmulatorWindow(); + + static std::unique_ptr Create(Emulator* emulator); + + Emulator* emulator() const { return emulator_; } + ui::Loop* loop() const { return loop_.get(); } + ui::Window* window() const { return window_.get(); } + + private: + explicit EmulatorWindow(Emulator* emulator); + + bool Initialize(); + void UpdateTitle(); + + void CpuTimeScalarReset(); + void CpuTimeScalarSetHalf(); + void CpuTimeScalarSetDouble(); + void GpuTraceFrame(); + void GpuClearCaches(); + void ToggleFullscreen(); + void ShowHelpWebsite(); + + Emulator* emulator_; + std::unique_ptr loop_; + std::unique_ptr window_; +}; + +} // namespace xe + +#endif // XENIA_EMULATOR_WINDOW_H_ diff --git a/src/xenia/gpu/gl4/command_processor.cc b/src/xenia/gpu/gl4/command_processor.cc index 7ebc0245a..c75d2ede4 100644 --- a/src/xenia/gpu/gl4/command_processor.cc +++ b/src/xenia/gpu/gl4/command_processor.cc @@ -71,8 +71,6 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system) active_pixel_shader_(nullptr), active_framebuffer_(nullptr), last_framebuffer_texture_(0), - last_swap_width_(0), - last_swap_height_(0), point_list_geometry_program_(0), rect_list_geometry_program_(0), quad_list_geometry_program_(0), @@ -83,7 +81,7 @@ CommandProcessor::CommandProcessor(GL4GraphicsSystem* graphics_system) CommandProcessor::~CommandProcessor() { CloseHandle(write_ptr_index_event_); } bool CommandProcessor::Initialize( - std::unique_ptr context) { + std::unique_ptr context) { context_ = std::move(context); worker_running_ = true; @@ -197,7 +195,7 @@ void CommandProcessor::WorkerThreadMain() { // We've run out of commands to execute. // We spin here waiting for new ones, as the overhead of waiting on our // event is too high. - // PrepareForWait(); + PrepareForWait(); do { // TODO(benvanik): if we go longer than Nms, switch to waiting? // It'll keep us from burning power. @@ -209,7 +207,7 @@ void CommandProcessor::WorkerThreadMain() { } while (worker_running_ && pending_fns_.empty() && (write_ptr_index == 0xBAADF00D || read_ptr_index_ == write_ptr_index)); - // ReturnFromWait(); + ReturnFromWait(); if (!worker_running_ || !pending_fns_.empty()) { continue; } @@ -576,18 +574,50 @@ void CommandProcessor::ReturnFromWait() { } } -void CommandProcessor::IssueSwap() { - IssueSwap(last_swap_width_, last_swap_height_); -} - void CommandProcessor::IssueSwap(uint32_t frontbuffer_width, uint32_t frontbuffer_height) { - if (!swap_handler_) { + SCOPE_profile_cpu_f("gpu"); + if (!swap_request_handler_) { return; } - auto& regs = *register_file_; - SwapParameters swap_params; + // If there was a swap pending we drop it on the floor. + // This prevents the display from pulling the backbuffer out from under us. + // If we skip a lot then we may need to buffer more, but as the display + // thread should be fairly idle that shouldn't happen. + if (!FLAGS_vsync) { + std::lock_guard lock(swap_state_.mutex); + if (swap_state_.pending) { + swap_state_.pending = false; + // TODO(benvanik): frame skip counter. + XELOGW("Skipped frame!"); + } + } else { + // Spin until no more pending swap. + while (true) { + { + std::lock_guard lock(swap_state_.mutex); + if (!swap_state_.pending) { + break; + } + } + xe::threading::MaybeYield(); + } + } + + // One-time initialization. + // TODO(benvanik): move someplace more sane? + if (!swap_state_.front_buffer_texture) { + std::lock_guard lock(swap_state_.mutex); + swap_state_.width = frontbuffer_width; + swap_state_.height = frontbuffer_height; + glCreateTextures(GL_TEXTURE_2D, 1, &swap_state_.front_buffer_texture); + glCreateTextures(GL_TEXTURE_2D, 1, &swap_state_.back_buffer_texture); + glTextureStorage2D(swap_state_.front_buffer_texture, 1, GL_RGBA8, + swap_state_.width, swap_state_.height); + glTextureStorage2D(swap_state_.back_buffer_texture, 1, GL_RGBA8, + swap_state_.width, swap_state_.height); + } // Lookup the framebuffer in the recently-resolved list. // TODO(benvanik): make this much more sophisticated. @@ -595,20 +625,34 @@ void CommandProcessor::IssueSwap(uint32_t frontbuffer_width, // TODO(benvanik): handle dirty cases (resolved to sysmem, touched). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // HACK: just use whatever our current framebuffer is. - swap_params.framebuffer_texture = last_framebuffer_texture_; - /*swap_params.framebuffer_texture = active_framebuffer_ + GLuint framebuffer_texture = last_framebuffer_texture_; + /*GLuint framebuffer_texture = active_framebuffer_ ? active_framebuffer_->color_targets[0] : last_framebuffer_texture_;*/ - // Frontbuffer dimensions, if valid. - swap_params.x = 0; - swap_params.y = 0; - swap_params.width = frontbuffer_width ? frontbuffer_width : 1280; - swap_params.height = frontbuffer_height ? frontbuffer_height : 720; + // Copy the the given framebuffer to the current backbuffer. + Rect2D src_rect(0, 0, frontbuffer_width ? frontbuffer_width : 1280, + frontbuffer_height ? frontbuffer_height : 720); + Rect2D dest_rect(0, 0, swap_state_.width, swap_state_.height); + reinterpret_cast(context_.get()) + ->blitter() + ->CopyColorTexture2D(framebuffer_texture, src_rect, + swap_state_.back_buffer_texture, dest_rect, + GL_LINEAR); - PrepareForWait(); - swap_handler_(swap_params); - ReturnFromWait(); + // Need to finish to be sure the other context sees the right data. + // TODO(benvanik): prevent this? fences? + glFinish(); + + { + // Set pending so that the display will swap the next time it can. + std::lock_guard lock(swap_state_.mutex); + swap_state_.pending = true; + } + + // Notify the display a swap is pending so that our changes are picked up. + // It does the actual front/back buffer swap. + swap_request_handler_(); // Remove any dead textures, etc. texture_cache_.Scavenge(); @@ -964,8 +1008,6 @@ bool CommandProcessor::ExecutePacketType3_XE_SWAP(RingbufferReader* reader, uint32_t frontbuffer_width = reader->Read(); uint32_t frontbuffer_height = reader->Read(); reader->Advance(count - 4); - last_swap_width_ = frontbuffer_width; - last_swap_height_ = frontbuffer_height; // Ensure we issue any pending draws. draw_batcher_.Flush(DrawBatcher::FlushMode::kMakeCoherent); @@ -2757,6 +2799,8 @@ bool CommandProcessor::IssueCopy() { // TODO(benvanik): copy to staging texture then PBO back? void* ptr = memory_->TranslatePhysical(copy_dest_base); + auto blitter = static_cast(context_.get())->blitter(); + // Make active so glReadPixels reads from us. switch (copy_command) { case CopyCommand::kRaw: { @@ -2766,8 +2810,8 @@ bool CommandProcessor::IssueCopy() { // Source from a bound render target. // TODO(benvanik): RAW copy. last_framebuffer_texture_ = texture_cache_.CopyTexture( - context_->blitter(), copy_dest_base, dest_logical_width, - dest_logical_height, dest_block_width, dest_block_height, + blitter, copy_dest_base, dest_logical_width, dest_logical_height, + dest_block_width, dest_block_height, ColorFormatToTextureFormat(copy_dest_format), copy_dest_swap ? true : false, color_targets[copy_src_select], src_rect, dest_rect); @@ -2777,11 +2821,10 @@ bool CommandProcessor::IssueCopy() { } else { // Source from the bound depth/stencil target. // TODO(benvanik): RAW copy. - texture_cache_.CopyTexture(context_->blitter(), copy_dest_base, - dest_logical_width, dest_logical_height, - dest_block_width, dest_block_height, - src_format, copy_dest_swap ? true : false, - depth_target, src_rect, dest_rect); + texture_cache_.CopyTexture( + blitter, copy_dest_base, dest_logical_width, dest_logical_height, + dest_block_width, dest_block_height, src_format, + copy_dest_swap ? true : false, depth_target, src_rect, dest_rect); if (!FLAGS_disable_framebuffer_readback) { // glReadPixels(x, y, w, h, GL_DEPTH_STENCIL, read_type, ptr); } @@ -2794,8 +2837,8 @@ bool CommandProcessor::IssueCopy() { // Either copy the readbuffer into an existing texture or create a new // one in the cache so we can service future upload requests. last_framebuffer_texture_ = texture_cache_.ConvertTexture( - context_->blitter(), copy_dest_base, dest_logical_width, - dest_logical_height, dest_block_width, dest_block_height, + blitter, copy_dest_base, dest_logical_width, dest_logical_height, + dest_block_width, dest_block_height, ColorFormatToTextureFormat(copy_dest_format), copy_dest_swap ? true : false, color_targets[copy_src_select], src_rect, dest_rect); @@ -2804,11 +2847,10 @@ bool CommandProcessor::IssueCopy() { } } else { // Source from the bound depth/stencil target. - texture_cache_.ConvertTexture(context_->blitter(), copy_dest_base, - dest_logical_width, dest_logical_height, - dest_block_width, dest_block_height, - src_format, copy_dest_swap ? true : false, - depth_target, src_rect, dest_rect); + texture_cache_.ConvertTexture( + blitter, copy_dest_base, dest_logical_width, dest_logical_height, + dest_block_width, dest_block_height, src_format, + copy_dest_swap ? true : false, depth_target, src_rect, dest_rect); if (!FLAGS_disable_framebuffer_readback) { // glReadPixels(x, y, w, h, GL_DEPTH_STENCIL, read_type, ptr); } diff --git a/src/xenia/gpu/gl4/command_processor.h b/src/xenia/gpu/gl4/command_processor.h index ef0f93dae..f096ed142 100644 --- a/src/xenia/gpu/gl4/command_processor.h +++ b/src/xenia/gpu/gl4/command_processor.h @@ -42,13 +42,18 @@ namespace gl4 { class GL4GraphicsSystem; -struct SwapParameters { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; - - GLuint framebuffer_texture; +struct SwapState { + // Lock must be held when changing data in this structure. + xe::mutex mutex; + // Dimensions of the framebuffer textures. Should match window size. + uint32_t width = 0; + uint32_t height = 0; + // Current front buffer, being drawn to the screen. + GLuint front_buffer_texture = 0; + // Current back buffer, being updated by the CP. + GLuint back_buffer_texture = 0; + // Whether the back buffer is dirty and a swap is pending. + bool pending = false; }; enum class SwapMode { @@ -61,22 +66,23 @@ class CommandProcessor { CommandProcessor(GL4GraphicsSystem* graphics_system); ~CommandProcessor(); - typedef std::function SwapHandler; - void set_swap_handler(SwapHandler fn) { swap_handler_ = fn; } - uint32_t counter() const { return counter_; } void increment_counter() { counter_++; } - bool Initialize(std::unique_ptr context); + bool Initialize(std::unique_ptr context); void Shutdown(); void CallInThread(std::function fn); void ClearCaches(); + SwapState& swap_state() { return swap_state_; } void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; } - void IssueSwap(); void IssueSwap(uint32_t frontbuffer_width, uint32_t frontbuffer_height); + void set_swap_request_handler(std::function fn) { + swap_request_handler_ = fn; + } + void RequestFrameTrace(const std::wstring& root_path); void BeginTracing(const std::wstring& root_path); void EndTracing(); @@ -238,11 +244,11 @@ class CommandProcessor { std::atomic worker_running_; kernel::object_ref worker_thread_; - std::unique_ptr context_; - SwapHandler swap_handler_; - std::queue> pending_fns_; - + std::unique_ptr context_; SwapMode swap_mode_; + SwapState swap_state_; + std::function swap_request_handler_; + std::queue> pending_fns_; uint32_t counter_; @@ -266,8 +272,6 @@ class CommandProcessor { GL4Shader* active_pixel_shader_; CachedFramebuffer* active_framebuffer_; GLuint last_framebuffer_texture_; - uint32_t last_swap_width_; - uint32_t last_swap_height_; std::vector cached_framebuffers_; std::vector cached_color_render_targets_; diff --git a/src/xenia/gpu/gl4/gl4_graphics_system.cc b/src/xenia/gpu/gl4/gl4_graphics_system.cc index 852dcc6fb..37955a96a 100644 --- a/src/xenia/gpu/gl4/gl4_graphics_system.cc +++ b/src/xenia/gpu/gl4/gl4_graphics_system.cc @@ -19,7 +19,8 @@ #include "xenia/gpu/gl4/gl4_gpu_flags.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/tracing.h" -#include "xenia/ui/gl/gl_profiler_display.h" +#include "xenia/profiling.h" +#include "xenia/ui/window.h" namespace xe { namespace gpu { @@ -47,6 +48,14 @@ std::unique_ptr GL4GraphicsSystem::Create(Emulator* emulator) { return std::make_unique(emulator); } +std::unique_ptr GL4GraphicsSystem::CreateContext( + ui::Window* target_window) { + // Setup the GL control that actually does the drawing. + // We run here in the loop and only touch it (and its context) on this + // thread. That means some sync-fu when we want to swap. + return xe::ui::gl::GLContext::Create(target_window); +} + GL4GraphicsSystem::GL4GraphicsSystem(Emulator* emulator) : GraphicsSystem(emulator), worker_running_(false) {} @@ -54,38 +63,36 @@ GL4GraphicsSystem::~GL4GraphicsSystem() = default; X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor, ui::Loop* target_loop, - ui::PlatformWindow* target_window) { + ui::Window* target_window) { auto result = GraphicsSystem::Setup(processor, target_loop, target_window); if (result) { return result; } + display_context_ = + reinterpret_cast(target_window->context()); + + // Watch for paint requests to do our swap. + target_window->on_painting.AddListener( + [this](xe::ui::UIEvent& e) { Swap(e); }); + // Create rendering control. // This must happen on the UI thread. - xe::threading::Fence control_ready_fence; - std::unique_ptr processor_context; - target_loop_->Post([&]() { - // Setup the GL control that actually does the drawing. - // We run here in the loop and only touch it (and its context) on this - // thread. That means some sync-fu when we want to swap. - control_ = std::make_unique(target_loop_); - target_window_->AddChild(control_.get()); - + std::unique_ptr processor_context; + target_loop_->PostSynchronous([&]() { // Setup the GL context the command processor will do all its drawing in. - // It's shared with the control context so that we can resolve framebuffers + // It's shared with the display context so that we can resolve framebuffers // from it. - processor_context = control_->context()->CreateShared(); - - { - xe::ui::gl::GLContextLock context_lock(control_->context()); - auto profiler_display = - std::make_unique(control_.get()); - Profiler::set_display(std::move(profiler_display)); - } - - control_ready_fence.Signal(); + processor_context = display_context_->CreateShared(); + processor_context->ClearCurrent(); }); - control_ready_fence.Wait(); + if (!processor_context) { + XEFATAL( + "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " + "you have the latest drivers for your GPU and that it supports OpenGL " + "4.5. See http://xenia.jp/faq/ for more information."); + return X_STATUS_UNSUCCESSFUL; + } // Create command processor. This will spin up a thread to process all // incoming ringbuffer packets. @@ -94,8 +101,8 @@ X_STATUS GL4GraphicsSystem::Setup(cpu::Processor* processor, XELOGE("Unable to initialize command processor"); return X_STATUS_UNSUCCESSFUL; } - command_processor_->set_swap_handler( - [this](const SwapParameters& swap_params) { SwapHandler(swap_params); }); + command_processor_->set_swap_request_handler( + [this]() { target_window_->Invalidate(); }); // Let the processor know we want register access callbacks. memory_->AddVirtualMappedRange( @@ -144,7 +151,6 @@ void GL4GraphicsSystem::Shutdown() { // TODO(benvanik): remove mapped range. command_processor_.reset(); - control_.reset(); GraphicsSystem::Shutdown(); } @@ -159,10 +165,6 @@ void GL4GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr, command_processor_->EnableReadPointerWriteBack(ptr, block_size); } -void GL4GraphicsSystem::RequestSwap() { - command_processor_->CallInThread([&]() { command_processor_->IssueSwap(); }); -} - void GL4GraphicsSystem::RequestFrameTrace() { command_processor_->RequestFrameTrace(xe::to_wstring(FLAGS_trace_gpu_prefix)); } @@ -268,6 +270,7 @@ void GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, size_t trace_size, } command_processor_->set_swap_mode(SwapMode::kNormal); + command_processor_->IssueSwap(1280, 720); }); } @@ -288,22 +291,29 @@ void GL4GraphicsSystem::MarkVblank() { DispatchInterruptCallback(0, 2); } -void GL4GraphicsSystem::SwapHandler(const SwapParameters& swap_params) { - SCOPE_profile_cpu_f("gpu"); - - // Swap requested. Synchronously post a request to the loop so that - // we do the swap in the right thread. - control_->SynchronousRepaint([this, swap_params]() { - if (!swap_params.framebuffer_texture) { - // no-op. - return; +void GL4GraphicsSystem::Swap(xe::ui::UIEvent& e) { + // Check for pending swap. + auto& swap_state = command_processor_->swap_state(); + { + std::lock_guard lock(swap_state.mutex); + if (swap_state.pending) { + swap_state.pending = false; + std::swap(swap_state.front_buffer_texture, + swap_state.back_buffer_texture); } - Rect2D src_rect(swap_params.x, swap_params.y, swap_params.width, - swap_params.height); - Rect2D dest_rect(0, 0, control_->width(), control_->height()); - control_->context()->blitter()->BlitTexture2D( - swap_params.framebuffer_texture, src_rect, dest_rect, GL_LINEAR); - }); + } + + if (!swap_state.front_buffer_texture) { + // Not yet ready. + return; + } + + // Blit the frontbuffer. + display_context_->blitter()->BlitTexture2D( + swap_state.front_buffer_texture, + Rect2D(0, 0, swap_state.width, swap_state.height), + Rect2D(0, 0, target_window_->width(), target_window_->height()), + GL_LINEAR); } uint64_t GL4GraphicsSystem::ReadRegister(uint32_t addr) { diff --git a/src/xenia/gpu/gl4/gl4_graphics_system.h b/src/xenia/gpu/gl4/gl4_graphics_system.h index 8137ea8c6..206651cd2 100644 --- a/src/xenia/gpu/gl4/gl4_graphics_system.h +++ b/src/xenia/gpu/gl4/gl4_graphics_system.h @@ -16,7 +16,7 @@ #include "xenia/gpu/graphics_system.h" #include "xenia/gpu/register_file.h" #include "xenia/kernel/objects/xthread.h" -#include "xenia/ui/gl/wgl_control.h" +#include "xenia/ui/gl/gl_context.h" namespace xe { namespace gpu { @@ -28,9 +28,11 @@ class GL4GraphicsSystem : public GraphicsSystem { ~GL4GraphicsSystem() override; static std::unique_ptr Create(Emulator* emulator); + std::unique_ptr CreateContext( + ui::Window* target_window) override; X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop, - ui::PlatformWindow* target_window) override; + ui::Window* target_window) override; void Shutdown() override; RegisterFile* register_file() { return ®ister_file_; } @@ -41,8 +43,6 @@ class GL4GraphicsSystem : public GraphicsSystem { void InitializeRingBuffer(uint32_t ptr, uint32_t page_count) override; void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) override; - void RequestSwap() override; - void RequestFrameTrace() override; void BeginTracing() override; void EndTracing() override; @@ -52,7 +52,7 @@ class GL4GraphicsSystem : public GraphicsSystem { private: void MarkVblank(); - void SwapHandler(const SwapParameters& swap_params); + void Swap(xe::ui::UIEvent& e); uint64_t ReadRegister(uint32_t addr); void WriteRegister(uint32_t addr, uint64_t value); @@ -67,7 +67,8 @@ class GL4GraphicsSystem : public GraphicsSystem { RegisterFile register_file_; std::unique_ptr command_processor_; - std::unique_ptr control_; + + xe::ui::gl::GLContext* display_context_ = nullptr; std::atomic worker_running_; kernel::object_ref worker_thread_; diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index d5233d250..167d109e8 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -43,7 +43,7 @@ GraphicsSystem::GraphicsSystem(Emulator* emulator) : emulator_(emulator) {} GraphicsSystem::~GraphicsSystem() = default; X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, ui::Loop* target_loop, - ui::PlatformWindow* target_window) { + ui::Window* target_window) { processor_ = processor; memory_ = processor->memory(); target_loop_ = target_loop; diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index a77654c45..5556da33c 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -17,7 +17,7 @@ #include "xenia/cpu/processor.h" #include "xenia/memory.h" #include "xenia/ui/loop.h" -#include "xenia/ui/platform.h" +#include "xenia/ui/window.h" #include "xenia/xbox.h" namespace xe { @@ -32,13 +32,15 @@ class GraphicsSystem { virtual ~GraphicsSystem(); static std::unique_ptr Create(Emulator* emulator); + virtual std::unique_ptr CreateContext( + ui::Window* target_window) = 0; Emulator* emulator() const { return emulator_; } Memory* memory() const { return memory_; } cpu::Processor* processor() const { return processor_; } virtual X_STATUS Setup(cpu::Processor* processor, ui::Loop* target_loop, - ui::PlatformWindow* target_window); + ui::Window* target_window); virtual void Shutdown(); void SetInterruptCallback(uint32_t callback, uint32_t user_data); @@ -46,8 +48,6 @@ class GraphicsSystem { virtual void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size) = 0; - virtual void RequestSwap() = 0; - void DispatchInterruptCallback(uint32_t source, uint32_t cpu); virtual void RequestFrameTrace() {} @@ -68,7 +68,7 @@ class GraphicsSystem { Memory* memory_ = nullptr; cpu::Processor* processor_ = nullptr; ui::Loop* target_loop_ = nullptr; - ui::PlatformWindow* target_window_ = nullptr; + ui::Window* target_window_ = nullptr; uint32_t interrupt_callback_ = 0; uint32_t interrupt_callback_data_ = 0; diff --git a/src/xenia/gpu/xe-gpu-trace-viewer.cc b/src/xenia/gpu/xe-gpu-trace-viewer.cc index 98b9cf36b..2fdb35773 100644 --- a/src/xenia/gpu/xe-gpu-trace-viewer.cc +++ b/src/xenia/gpu/xe-gpu-trace-viewer.cc @@ -24,6 +24,7 @@ #include "xenia/gpu/xenos.h" #include "xenia/profiling.h" #include "xenia/ui/gl/gl_context.h" +#include "xenia/ui/window.h" // HACK: until we have another impl, we just use gl4 directly. #include "xenia/gpu/gl4/command_processor.h" @@ -842,7 +843,7 @@ class TracePlayer : public TraceReader { int current_command_index_; }; -void DrawControllerUI(xe::ui::PlatformWindow* window, TracePlayer& player, +void DrawControllerUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCond_FirstUseEver); if (!ImGui::Begin("Controller", nullptr, ImVec2(340, 60))) { @@ -883,7 +884,7 @@ void DrawControllerUI(xe::ui::PlatformWindow* window, TracePlayer& player, ImGui::End(); } -void DrawCommandListUI(xe::ui::PlatformWindow* window, TracePlayer& player, +void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCond_FirstUseEver); if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) { @@ -1027,9 +1028,8 @@ ShaderDisplayType DrawShaderTypeUI() { return shader_display_type; } -void DrawShaderUI(xe::ui::PlatformWindow* window, TracePlayer& player, - Memory* memory, gl4::GL4Shader* shader, - ShaderDisplayType display_type) { +void DrawShaderUI(xe::ui::Window* window, TracePlayer& player, Memory* memory, + gl4::GL4Shader* shader, ShaderDisplayType display_type) { // Must be prepared for advanced display modes. if (display_type != ShaderDisplayType::kUcode) { if (!shader->has_prepared()) { @@ -1393,8 +1393,7 @@ static const char* kEndiannessNames[] = { "unspecified endianness", "8-in-16", "8-in-32", "16-in-32", }; -void DrawStateUI(xe::ui::PlatformWindow* window, TracePlayer& player, - Memory* memory) { +void DrawStateUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { auto gs = static_cast(player.graphics_system()); auto cp = gs->command_processor(); auto& regs = *gs->register_file(); @@ -2033,8 +2032,8 @@ void DrawStateUI(xe::ui::PlatformWindow* window, TracePlayer& player, ImGui::End(); } -void DrawPacketDisassemblerUI(xe::ui::PlatformWindow* window, - TracePlayer& player, Memory* memory) { +void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player, + Memory* memory) { ImGui::SetNextWindowCollapsed(true, ImGuiSetCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5), ImGuiSetCond_FirstUseEver); @@ -2175,8 +2174,7 @@ void DrawPacketDisassemblerUI(xe::ui::PlatformWindow* window, ImGui::End(); } -void DrawUI(xe::ui::PlatformWindow* window, TracePlayer& player, - Memory* memory) { +void DrawUI(xe::ui::Window* window, TracePlayer& player, Memory* memory) { // ImGui::ShowTestWindow(); DrawControllerUI(window, player, memory); @@ -2189,129 +2187,151 @@ void ImImpl_Setup(); void ImImpl_Shutdown(); int trace_viewer_main(std::vector& args) { - // Create the emulator. + // Create the emulator but don't initialize so we can setup the window. auto emulator = std::make_unique(L""); - X_STATUS result = emulator->Setup(); + + // Main emulator display window. + auto loop = ui::Loop::Create(); + auto window = xe::ui::Window::Create(loop.get(), L"xe-gpu-trace-viewer"); + loop->PostSynchronous([&window]() { + xe::threading::set_name("Win32 Loop"); + if (!window->Initialize()) { + XEFATAL("Failed to initialize main window"); + exit(1); + } + }); + window->on_closed.AddListener([&loop](xe::ui::UIEvent& e) { + loop->Quit(); + XELOGI("User-initiated death!"); + exit(1); + }); + loop->on_quit.AddListener([&window](xe::ui::UIEvent& e) { window.reset(); }); + window->Resize(1920, 1200); + + X_STATUS result = emulator->Setup(window.get()); if (XFAILED(result)) { XELOGE("Failed to setup emulator: %.8X", result); return 1; } // Grab path from the flag or unnamed argument. - if (!FLAGS_target_trace_file.empty() || args.size() >= 2) { - std::wstring path; - if (!FLAGS_target_trace_file.empty()) { - // Passed as a named argument. - // TODO(benvanik): find something better than gflags that supports - // unicode. - path = xe::to_wstring(FLAGS_target_trace_file); - } else { - // Passed as an unnamed argument. - path = args[1]; - } - // Normalize the path and make absolute. - auto abs_path = xe::to_absolute_path(path); - - auto window = emulator->display_window(); - auto loop = window->loop(); - auto file_name = xe::find_name_from_path(path); - window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name); - - auto graphics_system = emulator->graphics_system(); - Profiler::set_display(nullptr); - - TracePlayer player(loop, emulator->graphics_system()); - if (!player.Open(abs_path)) { - XELOGE("Could not load trace file"); - return 1; - } - - auto control = window->child(0); - control->on_key_char.AddListener([graphics_system](xe::ui::KeyEvent& e) { - auto& io = ImGui::GetIO(); - if (e.key_code() > 0 && e.key_code() < 0x10000) { - if (e.key_code() == 0x74 /* VK_F5 */) { - graphics_system->ClearCaches(); - } else { - io.AddInputCharacter(e.key_code()); - } - } - e.set_handled(true); - }); - control->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e.x()), float(e.y())); - switch (e.button()) { - case xe::ui::MouseEvent::Button::kLeft: - io.MouseDown[0] = true; - break; - case xe::ui::MouseEvent::Button::kRight: - io.MouseDown[1] = true; - break; - } - }); - control->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e.x()), float(e.y())); - }); - control->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e.x()), float(e.y())); - switch (e.button()) { - case xe::ui::MouseEvent::Button::kLeft: - io.MouseDown[0] = false; - break; - case xe::ui::MouseEvent::Button::kRight: - io.MouseDown[1] = false; - break; - } - }); - control->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) { - auto& io = ImGui::GetIO(); - io.MousePos = ImVec2(float(e.x()), float(e.y())); - io.MouseWheel += float(e.dy() / 120.0f); - }); - - control->on_paint.AddListener([&](xe::ui::UIEvent& e) { - static bool imgui_setup = false; - if (!imgui_setup) { - ImImpl_Setup(); - imgui_setup = true; - } - auto& io = ImGui::GetIO(); - auto current_ticks = Clock::QueryHostTickCount(); - static uint64_t last_ticks = 0; - io.DeltaTime = - (current_ticks - last_ticks) / float(Clock::host_tick_frequency()); - last_ticks = current_ticks; - - io.DisplaySize = - ImVec2(float(e.control()->width()), float(e.control()->height())); - - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - - ImGui::NewFrame(); - - DrawUI(window, player, emulator->memory()); - - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - ImGui::Render(); - - graphics_system->RequestSwap(); - }); - graphics_system->RequestSwap(); - - // Wait until we are exited. - emulator->display_window()->loop()->AwaitQuit(); - - ImImpl_Shutdown(); + if (FLAGS_target_trace_file.empty() && args.size() < 2) { + XELOGE("No trace file specified"); + return 1; } + std::wstring path; + if (!FLAGS_target_trace_file.empty()) { + // Passed as a named argument. + // TODO(benvanik): find something better than gflags that supports + // unicode. + path = xe::to_wstring(FLAGS_target_trace_file); + } else { + // Passed as an unnamed argument. + path = args[1]; + } + // Normalize the path and make absolute. + auto abs_path = xe::to_absolute_path(path); + + auto file_name = xe::find_name_from_path(path); + window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name); + + auto graphics_system = emulator->graphics_system(); + Profiler::set_display(nullptr); + + TracePlayer player(loop.get(), emulator->graphics_system()); + if (!player.Open(abs_path)) { + XELOGE("Could not load trace file"); + return 1; + } + + window->on_key_char.AddListener([graphics_system](xe::ui::KeyEvent& e) { + auto& io = ImGui::GetIO(); + if (e.key_code() > 0 && e.key_code() < 0x10000) { + if (e.key_code() == 0x74 /* VK_F5 */) { + graphics_system->ClearCaches(); + } else { + io.AddInputCharacter(e.key_code()); + } + } + e.set_handled(true); + }); + window->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) { + auto& io = ImGui::GetIO(); + io.MousePos = ImVec2(float(e.x()), float(e.y())); + switch (e.button()) { + case xe::ui::MouseEvent::Button::kLeft: + io.MouseDown[0] = true; + break; + case xe::ui::MouseEvent::Button::kRight: + io.MouseDown[1] = true; + break; + } + }); + window->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) { + auto& io = ImGui::GetIO(); + io.MousePos = ImVec2(float(e.x()), float(e.y())); + }); + window->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) { + auto& io = ImGui::GetIO(); + io.MousePos = ImVec2(float(e.x()), float(e.y())); + switch (e.button()) { + case xe::ui::MouseEvent::Button::kLeft: + io.MouseDown[0] = false; + break; + case xe::ui::MouseEvent::Button::kRight: + io.MouseDown[1] = false; + break; + } + }); + window->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) { + auto& io = ImGui::GetIO(); + io.MousePos = ImVec2(float(e.x()), float(e.y())); + io.MouseWheel += float(e.dy() / 120.0f); + }); + + window->on_painting.AddListener([&](xe::ui::UIEvent& e) { + static bool imgui_setup = false; + if (!imgui_setup) { + ImImpl_Setup(); + imgui_setup = true; + } + auto& io = ImGui::GetIO(); + auto current_ticks = Clock::QueryHostTickCount(); + static uint64_t last_ticks = 0; + io.DeltaTime = + (current_ticks - last_ticks) / float(Clock::host_tick_frequency()); + last_ticks = current_ticks; + + io.DisplaySize = + ImVec2(float(e.target()->width()), float(e.target()->height())); + + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + + ImGui::NewFrame(); + + DrawUI(window.get(), player, emulator->memory()); + + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + ImGui::Render(); + + // Continuous paint. + window->Invalidate(); + }); + window->Invalidate(); + + // Wait until we are exited. + loop->AwaitQuit(); + + ImImpl_Shutdown(); + emulator.reset(); + window.reset(); + loop.reset(); return 0; } diff --git a/src/xenia/kernel/xam_ui.cc b/src/xenia/kernel/xam_ui.cc index 81d2a9445..46e049a5f 100644 --- a/src/xenia/kernel/xam_ui.cc +++ b/src/xenia/kernel/xam_ui.cc @@ -67,7 +67,8 @@ SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context, TASKDIALOGCONFIG config = {0}; config.cbSize = sizeof(config); config.hInstance = GetModuleHandle(nullptr); - config.hwndParent = kernel_state->emulator()->display_window()->hwnd(); + 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; @@ -120,7 +121,7 @@ SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context, XELOGD("XamShowDirtyDiscErrorUI(%d)", user_index); int button_pressed = 0; - TaskDialog(kernel_state->emulator()->display_window()->hwnd(), + 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); diff --git a/src/xenia/profiling.cc b/src/xenia/profiling.cc index 4e1589840..1fc186158 100644 --- a/src/xenia/profiling.cc +++ b/src/xenia/profiling.cc @@ -33,6 +33,10 @@ std::unique_ptr Profiler::display_ = nullptr; #if XE_OPTION_PROFILING +bool Profiler::is_enabled() { return true; } + +bool Profiler::is_visible() { return MicroProfileIsDrawing(); } + void Profiler::Initialize() { // Custom groups. MicroProfileSetEnableAllGroups(false); @@ -168,6 +172,8 @@ void Profiler::Present() { #else +bool Profiler::is_enabled() { return false; } +bool Profiler::is_visible() { return false; } void Profiler::Initialize() {} void Profiler::Dump() {} void Profiler::Shutdown() {} diff --git a/src/xenia/profiling.h b/src/xenia/profiling.h index ee02e5910..6d8da30f3 100644 --- a/src/xenia/profiling.h +++ b/src/xenia/profiling.h @@ -148,11 +148,8 @@ class ProfilerDisplay { class Profiler { public: -#if XE_OPTION_PROFILING - static bool is_enabled() { return true; } -#else - static bool is_enabled() { return false; } -#endif // XE_OPTION_PROFILING + static bool is_enabled(); + static bool is_visible(); // Initializes the profiler. Call at startup. static void Initialize(); diff --git a/src/xenia/ui/control.cc b/src/xenia/ui/control.cc deleted file mode 100644 index eb9601b68..000000000 --- a/src/xenia/ui/control.cc +++ /dev/null @@ -1,132 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/control.h" - -#include "xenia/base/assert.h" - -namespace xe { -namespace ui { - -Control::Control(uint32_t flags) - : flags_(flags), - parent_(nullptr), - width_(0), - height_(0), - is_cursor_visible_(true), - is_enabled_(true), - is_visible_(true), - has_focus_(false) {} - -Control::~Control() { children_.clear(); } - -void Control::AddChild(Control* child_control) { - AddChild(ControlPtr(child_control, [](Control* control) {})); -} - -void Control::AddChild(std::unique_ptr child_control) { - AddChild(ControlPtr(child_control.release(), - [](Control* control) { delete control; })); -} - -void Control::AddChild(ControlPtr control) { - assert_null(control->parent()); - control->parent_ = this; - auto control_ptr = control.get(); - children_.emplace_back(std::move(control)); - OnChildAdded(control_ptr); -} - -Control::ControlPtr Control::RemoveChild(Control* child_control) { - assert_true(child_control->parent() == this); - for (auto& it = children_.begin(); it != children_.end(); ++it) { - if (it->get() == child_control) { - auto control_ptr = std::move(*it); - child_control->parent_ = nullptr; - children_.erase(it); - OnChildRemoved(child_control); - return control_ptr; - } - } - return ControlPtr(nullptr, [](Control*) {}); -} - -void Control::Layout() { - auto e = UIEvent(this); - OnLayout(e); - for (auto& child_control : children_) { - child_control->OnLayout(e); - } -} - -void Control::OnResize(UIEvent& e) { on_resize(e); } - -void Control::OnLayout(UIEvent& e) { on_layout(e); } - -void Control::OnPaint(UIEvent& e) { on_paint(e); } - -void Control::OnVisible(UIEvent& e) { on_visible(e); } - -void Control::OnHidden(UIEvent& e) { on_hidden(e); } - -void Control::OnGotFocus(UIEvent& e) { on_got_focus(e); } - -void Control::OnLostFocus(UIEvent& e) { on_lost_focus(e); } - -void Control::OnKeyDown(KeyEvent& e) { - on_key_down(e); - if (parent_ && !e.is_handled()) { - parent_->OnKeyDown(e); - } -} - -void Control::OnKeyUp(KeyEvent& e) { - on_key_up(e); - if (parent_ && !e.is_handled()) { - parent_->OnKeyUp(e); - } -} - -void Control::OnKeyChar(KeyEvent& e) { - on_key_char(e); - if (parent_ && !e.is_handled()) { - parent_->OnKeyChar(e); - } -} - -void Control::OnMouseDown(MouseEvent& e) { - on_mouse_down(e); - if (parent_ && !e.is_handled()) { - parent_->OnMouseDown(e); - } -} - -void Control::OnMouseMove(MouseEvent& e) { - on_mouse_move(e); - if (parent_ && !e.is_handled()) { - parent_->OnMouseMove(e); - } -} - -void Control::OnMouseUp(MouseEvent& e) { - on_mouse_up(e); - if (parent_ && !e.is_handled()) { - parent_->OnMouseUp(e); - } -} - -void Control::OnMouseWheel(MouseEvent& e) { - on_mouse_wheel(e); - if (parent_ && !e.is_handled()) { - parent_->OnMouseWheel(e); - } -} - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/control.h b/src/xenia/ui/control.h deleted file mode 100644 index becdc17b6..000000000 --- a/src/xenia/ui/control.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_CONTROL_H_ -#define XENIA_UI_CONTROL_H_ - -#include -#include - -#include "xenia/base/delegate.h" -#include "xenia/ui/ui_event.h" - -namespace xe { -namespace ui { - -class Control { - public: - typedef std::unique_ptr ControlPtr; - enum Flags { - // Control paints itself, so disable platform drawing. - kFlagOwnPaint = 1 << 1, - }; - - virtual ~Control(); - - Control* parent() const { return parent_; } - - size_t child_count() const { return children_.size(); } - Control* child(size_t i) const { return children_[i].get(); } - void AddChild(Control* child_control); - void AddChild(std::unique_ptr child_control); - void AddChild(ControlPtr child_control); - ControlPtr RemoveChild(Control* child_control); - - int32_t width() const { return width_; } - int32_t height() const { return height_; } - virtual void Resize(int32_t width, int32_t height) = 0; - virtual void Resize(int32_t left, int32_t top, int32_t right, - int32_t bottom) = 0; - void ResizeToFill() { ResizeToFill(0, 0, 0, 0); } - virtual void ResizeToFill(int32_t pad_left, int32_t pad_top, - int32_t pad_right, int32_t pad_bottom) = 0; - void Layout(); - virtual void Invalidate() {} - - // TODO(benvanik): colors/brushes/etc. - // TODO(benvanik): fonts. - - bool is_cursor_visible() const { return is_cursor_visible_; } - virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; } - - bool is_enabled() const { return is_enabled_; } - virtual void set_enabled(bool value) { is_enabled_ = value; } - - bool is_visible() const { return is_visible_; } - virtual void set_visible(bool value) { is_visible_ = value; } - - bool has_focus() const { return has_focus_; } - virtual void set_focus(bool value) { has_focus_ = value; } - - public: - Delegate on_resize; - Delegate on_layout; - Delegate on_paint; - - Delegate on_visible; - Delegate on_hidden; - - Delegate on_got_focus; - Delegate on_lost_focus; - - Delegate on_key_down; - Delegate on_key_up; - Delegate on_key_char; - - Delegate on_mouse_down; - Delegate on_mouse_move; - Delegate on_mouse_up; - Delegate on_mouse_wheel; - - protected: - explicit Control(uint32_t flags); - - virtual bool Create() { return true; } - virtual void Destroy() {} - - virtual void OnCreate() {} - virtual void OnDestroy() {} - - virtual void OnChildAdded(Control* child_control) {} - virtual void OnChildRemoved(Control* child_control) {} - - virtual void OnResize(UIEvent& e); - virtual void OnLayout(UIEvent& e); - virtual void OnPaint(UIEvent& e); - - virtual void OnVisible(UIEvent& e); - virtual void OnHidden(UIEvent& e); - - virtual void OnGotFocus(UIEvent& e); - virtual void OnLostFocus(UIEvent& e); - - virtual void OnKeyDown(KeyEvent& e); - virtual void OnKeyUp(KeyEvent& e); - virtual void OnKeyChar(KeyEvent& e); - - virtual void OnMouseDown(MouseEvent& e); - virtual void OnMouseMove(MouseEvent& e); - virtual void OnMouseUp(MouseEvent& e); - virtual void OnMouseWheel(MouseEvent& e); - - uint32_t flags_; - Control* parent_; - std::vector children_; - - int32_t width_; - int32_t height_; - - bool is_cursor_visible_; - bool is_enabled_; - bool is_visible_; - bool has_focus_; -}; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_CONTROL_H_ diff --git a/src/xenia/ui/elemental_control.h b/src/xenia/ui/elemental_control.h deleted file mode 100644 index 93c43fd76..000000000 --- a/src/xenia/ui/elemental_control.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2015 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_ELEMENTAL_CONTROL_H_ -#define XENIA_UI_ELEMENTAL_CONTROL_H_ - -#include - -#include "el/element.h" -#include "el/graphics/renderer.h" -#include "xenia/ui/control.h" -#include "xenia/ui/loop.h" -#include "xenia/ui/platform.h" - -namespace xe { -namespace ui { - -class ElementalControl : public PlatformControl { - public: - ElementalControl(Loop* loop, uint32_t flags); - ~ElementalControl() override; - - el::graphics::Renderer* renderer() const { return renderer_.get(); } - el::Element* root_element() const { return root_element_.get(); } - - bool LoadLanguage(std::string filename); - bool LoadSkin(std::string filename); - - protected: - using super = PlatformControl; - - bool InitializeElemental(Loop* loop, el::graphics::Renderer* renderer); - virtual std::unique_ptr CreateRenderer() = 0; - - bool Create() override; - void Destroy() override; - - void OnLayout(UIEvent& e) override; - void OnPaint(UIEvent& e) override; - - void OnGotFocus(UIEvent& e) override; - void OnLostFocus(UIEvent& e) override; - - el::ModifierKeys GetModifierKeys(); - - void OnKeyPress(KeyEvent& e, bool is_down, bool is_char); - bool CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, bool is_down); - void OnKeyDown(KeyEvent& e) override; - void OnKeyUp(KeyEvent& e) override; - void OnKeyChar(KeyEvent& e) override; - - void OnMouseDown(MouseEvent& e) override; - void OnMouseMove(MouseEvent& e) override; - void OnMouseUp(MouseEvent& e) override; - void OnMouseWheel(MouseEvent& e) override; - - Loop* loop_ = nullptr; - std::unique_ptr renderer_; - std::unique_ptr root_element_; - - uint32_t frame_count_ = 0; - uint32_t fps_ = 0; - uint64_t fps_update_time_ = 0; - uint64_t fps_frame_count_ = 0; - - bool modifier_shift_pressed_ = false; - bool modifier_cntrl_pressed_ = false; - bool modifier_alt_pressed_ = false; - bool modifier_super_pressed_ = false; - uint64_t last_click_time_ = 0; - int last_click_x_ = 0; - int last_click_y_ = 0; - int last_click_counter_ = 0; -}; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_ELEMENTAL_CONTROL_H_ diff --git a/src/xenia/ui/file_picker.h b/src/xenia/ui/file_picker.h index 1cd7553bb..c6884f23e 100644 --- a/src/xenia/ui/file_picker.h +++ b/src/xenia/ui/file_picker.h @@ -10,6 +10,7 @@ #ifndef XENIA_UI_FILE_PICKER_H_ #define XENIA_UI_FILE_PICKER_H_ +#include #include #include @@ -27,6 +28,8 @@ class FilePicker { kDirectory = 1, }; + static std::unique_ptr Create(); + FilePicker() : mode_(Mode::kOpen), type_(Type::kFile), diff --git a/src/xenia/ui/win32/win32_file_picker.cc b/src/xenia/ui/file_picker_win.cc similarity index 95% rename from src/xenia/ui/win32/win32_file_picker.cc rename to src/xenia/ui/file_picker_win.cc index ded97de33..eab1c0e92 100644 --- a/src/xenia/ui/win32/win32_file_picker.cc +++ b/src/xenia/ui/file_picker_win.cc @@ -7,14 +7,27 @@ ****************************************************************************** */ -#include "xenia/ui/win32/win32_file_picker.h" +#include "xenia/ui/file_picker.h" #include "xenia/base/platform.h" #include "xenia/base/assert.h" namespace xe { namespace ui { -namespace win32 { + +class Win32FilePicker : public FilePicker { + public: + Win32FilePicker(); + ~Win32FilePicker() override; + + bool Show(void* parent_window_handle) override; + + private: +}; + +std::unique_ptr FilePicker::Create() { + return std::make_unique(); +} class CDialogEventHandler : public IFileDialogEvents, public IFileDialogControlEvents { @@ -201,6 +214,5 @@ bool Win32FilePicker::Show(void* parent_window_handle) { return true; } -} // namespace win32 } // namespace ui } // namespace xe diff --git a/src/xenia/ui/gl/circular_buffer.cc b/src/xenia/ui/gl/circular_buffer.cc index 6039e1519..575a37b9e 100644 --- a/src/xenia/ui/gl/circular_buffer.cc +++ b/src/xenia/ui/gl/circular_buffer.cc @@ -11,7 +11,6 @@ #include "xenia/base/assert.h" #include "xenia/base/math.h" -#include "xenia/gpu/gl4/gl4_gpu_flags.h" namespace xe { namespace ui { diff --git a/src/xenia/ui/gl/gl4_elemental_renderer.cc b/src/xenia/ui/gl/gl4_elemental_renderer.cc index 1aa675c3f..7eba88d45 100644 --- a/src/xenia/ui/gl/gl4_elemental_renderer.cc +++ b/src/xenia/ui/gl/gl4_elemental_renderer.cc @@ -28,7 +28,7 @@ GL4ElementalRenderer::GL4Bitmap::GL4Bitmap(GLContext* context, : context_(context), renderer_(renderer) {} GL4ElementalRenderer::GL4Bitmap::~GL4Bitmap() { - GLContextLock lock(context_); + GraphicsContextLock lock(context_); // Must flush and unbind before we delete the texture. renderer_->FlushBitmap(this); @@ -69,7 +69,7 @@ GL4ElementalRenderer::GL4ElementalRenderer(GLContext* context) vertex_buffer_(max_vertex_batch_size() * sizeof(Vertex)) {} GL4ElementalRenderer::~GL4ElementalRenderer() { - GLContextLock lock(context_); + GraphicsContextLock lock(context_); vertex_buffer_.Shutdown(); glDeleteVertexArrays(1, &vao_); glDeleteProgram(program_); @@ -77,6 +77,7 @@ GL4ElementalRenderer::~GL4ElementalRenderer() { std::unique_ptr GL4ElementalRenderer::Create( GLContext* context) { + GraphicsContextLock lock(context); auto renderer = std::make_unique(context); if (!renderer->Initialize()) { XELOGE("Failed to initialize TurboBadger GL4 renderer"); diff --git a/src/xenia/ui/gl/gl_context.cc b/src/xenia/ui/gl/gl_context.cc index 2fb4f0329..02979439f 100644 --- a/src/xenia/ui/gl/gl_context.cc +++ b/src/xenia/ui/gl/gl_context.cc @@ -16,6 +16,9 @@ #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/profiling.h" +#include "xenia/ui/gl/gl_profiler_display.h" +#include "xenia/ui/gl/gl4_elemental_renderer.h" +#include "xenia/ui/window.h" DEFINE_bool(thread_safe_gl, false, "Only allow one GL context to be active at a time."); @@ -40,11 +43,20 @@ thread_local WGLEWContext* tls_wglew_context_ = nullptr; extern "C" GLEWContext* glewGetContext() { return tls_glew_context_; } extern "C" WGLEWContext* wglewGetContext() { return tls_wglew_context_; } -GLContext::GLContext() : hwnd_(nullptr), dc_(nullptr), glrc_(nullptr) {} +std::unique_ptr GLContext::Create(Window* target_window) { + auto context = std::unique_ptr(new GLContext(target_window)); + if (!context->Initialize(target_window)) { + return nullptr; + } + context->AssertExtensionsPresent(); + return context; +} -GLContext::GLContext(HWND hwnd, HGLRC glrc) - : hwnd_(hwnd), dc_(nullptr), glrc_(glrc) { - dc_ = GetDC(hwnd); +GLContext::GLContext(Window* target_window) : GraphicsContext(target_window) {} + +GLContext::GLContext(Window* target_window, HGLRC glrc) + : GraphicsContext(target_window), glrc_(glrc) { + dc_ = GetDC(HWND(target_window_->native_handle())); } GLContext::~GLContext() { @@ -55,13 +67,13 @@ GLContext::~GLContext() { wglDeleteContext(glrc_); } if (dc_) { - ReleaseDC(hwnd_, dc_); + ReleaseDC(HWND(target_window_->native_handle()), dc_); } } -bool GLContext::Initialize(HWND hwnd) { - hwnd_ = hwnd; - dc_ = GetDC(hwnd); +bool GLContext::Initialize(Window* target_window) { + target_window_ = target_window; + dc_ = GetDC(HWND(target_window_->native_handle())); PIXELFORMATDESCRIPTOR pfd = {0}; pfd.nSize = sizeof(pfd); @@ -152,12 +164,12 @@ bool GLContext::Initialize(HWND hwnd) { return true; } -std::unique_ptr GLContext::CreateShared() { +std::unique_ptr GLContext::CreateShared() { assert_not_null(glrc_); HGLRC new_glrc = nullptr; { - GLContextLock context_lock(this); + GraphicsContextLock context_lock(this); int context_flags = 0; // int profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; @@ -178,7 +190,8 @@ std::unique_ptr GLContext::CreateShared() { } } - auto new_context = std::make_unique(hwnd_, new_glrc); + auto new_context = + std::unique_ptr(new GLContext(target_window_, new_glrc)); if (!new_context->MakeCurrent()) { XELOGE("Could not make new GL context current"); return nullptr; @@ -205,7 +218,7 @@ std::unique_ptr GLContext::CreateShared() { new_context->ClearCurrent(); - return new_context; + return std::unique_ptr(new_context.release()); } void FatalGLError(std::string error) { @@ -369,6 +382,14 @@ void GLContext::SetupDebugging() { this); } +std::unique_ptr GLContext::CreateProfilerDisplay() { + return std::make_unique(target_window_); +} + +std::unique_ptr GLContext::CreateElementalRenderer() { + return GL4ElementalRenderer::Create(this); +} + bool GLContext::MakeCurrent() { SCOPE_profile_cpu_f("gpu"); if (FLAGS_thread_safe_gl) { @@ -399,6 +420,17 @@ void GLContext::ClearCurrent() { } } +void GLContext::BeginSwap() { + SCOPE_profile_cpu_i("gpu", "xe::ui::gl::GLContext::BeginSwap"); + float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f}; + glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color); +} + +void GLContext::EndSwap() { + SCOPE_profile_cpu_i("gpu", "xe::ui::gl::GLContext::EndSwap"); + SwapBuffers(dc()); +} + } // namespace gl } // namespace ui } // namespace xe diff --git a/src/xenia/ui/gl/gl_context.h b/src/xenia/ui/gl/gl_context.h index d8ae4f766..17b3b275b 100644 --- a/src/xenia/ui/gl/gl_context.h +++ b/src/xenia/ui/gl/gl_context.h @@ -16,6 +16,7 @@ #include "xenia/ui/gl/blitter.h" #include "xenia/ui/gl/gl.h" +#include "xenia/ui/graphics_context.h" DECLARE_bool(thread_safe_gl); @@ -23,25 +24,33 @@ namespace xe { namespace ui { namespace gl { -class GLContext { +class GLContext : public GraphicsContext { public: - GLContext(); - GLContext(HWND hwnd, HGLRC glrc); - ~GLContext(); + static std::unique_ptr Create(Window* target_window); - bool Initialize(HWND hwnd); - void AssertExtensionsPresent(); + ~GLContext() override; HDC dc() const { return dc_; } - std::unique_ptr CreateShared(); + std::unique_ptr CreateShared() override; + std::unique_ptr CreateProfilerDisplay() override; + std::unique_ptr CreateElementalRenderer() override; - bool MakeCurrent(); - void ClearCurrent(); + bool MakeCurrent() override; + void ClearCurrent() override; + + void BeginSwap() override; + void EndSwap() override; Blitter* blitter() { return &blitter_; } private: + GLContext(Window* target_window); + GLContext(Window* target_window, HGLRC glrc); + + bool Initialize(Window* target_window); + void AssertExtensionsPresent(); + void SetupDebugging(); void DebugMessage(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message); @@ -49,9 +58,8 @@ class GLContext { DebugMessageThunk(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* user_param); - HWND hwnd_; - HDC dc_; - HGLRC glrc_; + HDC dc_ = nullptr; + HGLRC glrc_ = nullptr; GLEWContext glew_context_; WGLEWContext wglew_context_; @@ -59,16 +67,6 @@ class GLContext { Blitter blitter_; }; -struct GLContextLock { - GLContextLock(GLContext* context) : context_(context) { - context_->MakeCurrent(); - } - ~GLContextLock() { context_->ClearCurrent(); } - - private: - GLContext* context_; -}; - } // namespace gl } // namespace ui } // namespace xe diff --git a/src/xenia/ui/gl/gl_profiler_display.cc b/src/xenia/ui/gl/gl_profiler_display.cc index 3ea38cdd0..5a3f070ff 100644 --- a/src/xenia/ui/gl/gl_profiler_display.cc +++ b/src/xenia/ui/gl/gl_profiler_display.cc @@ -132,47 +132,56 @@ const uint8_t profiler_font[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -GLProfilerDisplay::GLProfilerDisplay(xe::ui::gl::WGLControl* control) - : control_(control), - program_(0), - vao_(0), - font_texture_(0), - font_handle_(0), +GLProfilerDisplay::GLProfilerDisplay(xe::ui::Window* window) + : window_(window), vertex_buffer_(MICROPROFILE_MAX_VERTICES * sizeof(Vertex) * 10, - sizeof(Vertex)), - draw_command_count_(0) { + sizeof(Vertex)) { if (!SetupFont() || !SetupState() || !SetupShaders()) { // Hrm. assert_always(); } + window_->on_painted.AddListener([this](UIEvent& e) { Profiler::Present(); }); + // Pass through mouse events. - control->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) { - Profiler::OnMouseDown(e.button() == xe::ui::MouseEvent::Button::kLeft, - e.button() == xe::ui::MouseEvent::Button::kRight); - e.set_handled(true); + window_->on_mouse_down.AddListener([](xe::ui::MouseEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnMouseDown(e.button() == xe::ui::MouseEvent::Button::kLeft, + e.button() == xe::ui::MouseEvent::Button::kRight); + e.set_handled(true); + } }); - control->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) { - Profiler::OnMouseUp(); - e.set_handled(true); + window_->on_mouse_up.AddListener([](xe::ui::MouseEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnMouseUp(); + e.set_handled(true); + } }); - control->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) { - Profiler::OnMouseMove(e.x(), e.y()); - e.set_handled(true); + window_->on_mouse_move.AddListener([](xe::ui::MouseEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnMouseMove(e.x(), e.y()); + e.set_handled(true); + } }); - control->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) { - Profiler::OnMouseWheel(e.x(), e.y(), -e.dy()); - e.set_handled(true); + window_->on_mouse_wheel.AddListener([](xe::ui::MouseEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnMouseWheel(e.x(), e.y(), -e.dy()); + e.set_handled(true); + } }); // Watch for toggle/mode keys and such. - control->on_key_down.AddListener([](xe::ui::KeyEvent& e) { - Profiler::OnKeyDown(e.key_code()); - // e.set_handled(true); + window_->on_key_down.AddListener([](xe::ui::KeyEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnKeyDown(e.key_code()); + e.set_handled(true); + } }); - control->on_key_up.AddListener([](xe::ui::KeyEvent& e) { - Profiler::OnKeyUp(e.key_code()); - // e.set_handled(true); + window_->on_key_up.AddListener([](xe::ui::KeyEvent& e) { + if (Profiler::is_enabled()) { + Profiler::OnKeyUp(e.key_code()); + e.set_handled(true); + } }); } @@ -336,9 +345,9 @@ GLProfilerDisplay::~GLProfilerDisplay() { glDeleteProgram(program_); } -uint32_t GLProfilerDisplay::width() const { return control_->width(); } +uint32_t GLProfilerDisplay::width() const { return window_->width(); } -uint32_t GLProfilerDisplay::height() const { return control_->height(); } +uint32_t GLProfilerDisplay::height() const { return window_->height(); } void GLProfilerDisplay::Begin() { glEnablei(GL_BLEND, 0); @@ -347,7 +356,7 @@ void GLProfilerDisplay::Begin() { glDisable(GL_STENCIL_TEST); glDisable(GL_SCISSOR_TEST); - glViewport(0, 0, control_->width(), control_->height()); + glViewport(0, 0, width(), height()); float left = 0.0f; float right = float(width()); diff --git a/src/xenia/ui/gl/gl_profiler_display.h b/src/xenia/ui/gl/gl_profiler_display.h index ca029115b..b98dd35c3 100644 --- a/src/xenia/ui/gl/gl_profiler_display.h +++ b/src/xenia/ui/gl/gl_profiler_display.h @@ -12,8 +12,7 @@ #include "xenia/profiling.h" #include "xenia/ui/gl/circular_buffer.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/gl/wgl_control.h" +#include "xenia/ui/window.h" namespace xe { namespace ui { @@ -21,7 +20,7 @@ namespace gl { class GLProfilerDisplay : public ProfilerDisplay { public: - GLProfilerDisplay(xe::ui::gl::WGLControl* control); + GLProfilerDisplay(xe::ui::Window* window); virtual ~GLProfilerDisplay(); uint32_t width() const override; @@ -54,11 +53,11 @@ class GLProfilerDisplay : public ProfilerDisplay { void EndVertices(GLenum prim_type); void Flush(); - xe::ui::gl::WGLControl* control_; - GLuint program_; - GLuint vao_; - GLuint font_texture_; - GLuint64 font_handle_; + xe::ui::Window* window_ = nullptr; + GLuint program_ = 0; + GLuint vao_ = 0; + GLuint font_texture_ = 0; + GLuint64 font_handle_ = 0; CircularBuffer vertex_buffer_; static const size_t kMaxCommands = 32; @@ -67,13 +66,13 @@ class GLProfilerDisplay : public ProfilerDisplay { size_t vertex_offset; size_t vertex_count; } draw_commands_[kMaxCommands]; - uint32_t draw_command_count_; + uint32_t draw_command_count_ = 0; CircularBuffer::Allocation current_allocation_; struct { uint16_t char_offsets[256]; - } font_description_; + } font_description_ = {0}; }; } // namespace gl diff --git a/src/xenia/ui/gl/wgl_control.cc b/src/xenia/ui/gl/wgl_control.cc deleted file mode 100644 index 86abb78e4..000000000 --- a/src/xenia/ui/gl/wgl_control.cc +++ /dev/null @@ -1,125 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/gl/wgl_control.h" - -#include "xenia/base/assert.h" -#include "xenia/base/logging.h" -#include "xenia/profiling.h" - -namespace xe { -namespace ui { -namespace gl { - -WGLControl::WGLControl(Loop* loop) - : xe::ui::win32::Win32Control(Flags::kFlagOwnPaint), loop_(loop) {} - -WGLControl::~WGLControl() = default; - -bool WGLControl::Create() { - HINSTANCE hInstance = GetModuleHandle(nullptr); - - WNDCLASSEX wcex; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wcex.lpfnWndProc = Win32Control::WndProcThunk; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = nullptr; - wcex.hIconSm = nullptr; - wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wcex.lpszMenuName = nullptr; - wcex.lpszClassName = L"XeniaWglClass"; - if (!RegisterClassEx(&wcex)) { - XELOGE("WGL RegisterClassEx failed"); - return false; - } - - // Create window. - DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY; - DWORD window_ex_style = 0; - hwnd_ = - CreateWindowEx(window_ex_style, L"XeniaWglClass", L"Xenia", window_style, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - parent_hwnd(), nullptr, hInstance, this); - if (!hwnd_) { - XELOGE("WGL CreateWindow failed"); - return false; - } - - if (!context_.Initialize(hwnd_)) { - XEFATAL( - "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " - "you have the latest drivers for your GPU and that it supports OpenGL " - "4.5. See http://xenia.jp/faq/ for more information."); - return false; - } - - context_.AssertExtensionsPresent(); - - SetFocus(hwnd_); - - OnCreate(); - return true; -} - -void WGLControl::OnLayout(UIEvent& e) { Control::ResizeToFill(); } - -LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) { - switch (message) { - case WM_PAINT: { - invalidated_ = false; - ValidateRect(hWnd, nullptr); - SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::WM_PAINT"); - { - GLContextLock context_lock(&context_); - - float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f}; - glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color); - - if (current_paint_callback_) { - current_paint_callback_(); - current_paint_callback_ = nullptr; - } - - UIEvent e(this); - OnPaint(e); - - // TODO(benvanik): profiler present. - Profiler::Present(); - } - { - SCOPE_profile_cpu_i("gpu", "xe::gpu::gl4::WGLControl::SwapBuffers"); - SwapBuffers(context_.dc()); - } - return 0; - } break; - } - return Win32Control::WndProc(hWnd, message, wParam, lParam); -} - -void WGLControl::SynchronousRepaint(std::function paint_callback) { - SCOPE_profile_cpu_f("gpu"); - - // We may already have a pending paint from a previous request when we - // were minimized. We just overwrite it. - current_paint_callback_ = std::move(paint_callback); - - // This will not return until the WM_PAINT has completed. - // Note, if we are minimized this won't do anything. - RedrawWindow(hwnd(), nullptr, nullptr, - RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN); -} - -} // namespace gl -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/gl/wgl_control.h b/src/xenia/ui/gl/wgl_control.h deleted file mode 100644 index 322e85541..000000000 --- a/src/xenia/ui/gl/wgl_control.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_GL_WGL_CONTROL_H_ -#define XENIA_UI_GL_WGL_CONTROL_H_ - -#include - -#include "xenia/base/threading.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/loop.h" -#include "xenia/ui/win32/win32_control.h" - -namespace xe { -namespace ui { -namespace gl { - -class WGLControl : public xe::ui::win32::Win32Control { - public: - WGLControl(Loop* loop); - ~WGLControl() override; - - GLContext* context() { return &context_; } - - void SynchronousRepaint(std::function paint_callback); - - protected: - bool Create() override; - - void OnLayout(UIEvent& e) override; - - LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) override; - - private: - Loop* loop_; - GLContext context_; - std::function current_paint_callback_; -}; - -} // namespace gl -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_GL_WGL_CONTROL_H_ diff --git a/src/xenia/ui/gl/wgl_elemental_control.cc b/src/xenia/ui/gl/wgl_elemental_control.cc deleted file mode 100644 index 0ff7470a8..000000000 --- a/src/xenia/ui/gl/wgl_elemental_control.cc +++ /dev/null @@ -1,140 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2015 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/gl/wgl_elemental_control.h" - -#include - -#include "el/util/math.h" -#include "xenia/base/assert.h" -#include "xenia/base/logging.h" -#include "xenia/profiling.h" -#include "xenia/ui/gl/circular_buffer.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/gl/gl.h" -#include "xenia/ui/gl/gl4_elemental_renderer.h" - -namespace xe { -namespace ui { -namespace gl { - -WGLElementalControl::WGLElementalControl(Loop* loop) - : ElementalControl(loop, Flags::kFlagOwnPaint), loop_(loop) {} - -WGLElementalControl::~WGLElementalControl() = default; - -bool WGLElementalControl::Create() { - HINSTANCE hInstance = GetModuleHandle(nullptr); - - WNDCLASSEX wcex; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wcex.lpfnWndProc = Win32Control::WndProcThunk; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = nullptr; - wcex.hIconSm = nullptr; - wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wcex.lpszMenuName = nullptr; - wcex.lpszClassName = L"XeniaWglElementalClass"; - if (!RegisterClassEx(&wcex)) { - XELOGE("WGL RegisterClassEx failed"); - return false; - } - - // Create window. - DWORD window_style = WS_CHILD | WS_VISIBLE | SS_NOTIFY; - DWORD window_ex_style = 0; - hwnd_ = - CreateWindowEx(window_ex_style, L"XeniaWglElementalClass", L"Xenia", - window_style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, parent_hwnd(), nullptr, hInstance, this); - if (!hwnd_) { - XELOGE("WGL CreateWindow failed"); - return false; - } - - if (!context_.Initialize(hwnd_)) { - XEFATAL( - "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " - "you have the latest drivers for your GPU and that it supports OpenGL " - "4.5. See http://xenia.jp/faq/ for more information."); - return false; - } - - context_.AssertExtensionsPresent(); - - SetFocus(hwnd_); - - return super::Create(); -} - -std::unique_ptr WGLElementalControl::CreateRenderer() { - return GL4ElementalRenderer::Create(&context_); -} - -void WGLElementalControl::OnLayout(UIEvent& e) { - Control::ResizeToFill(); - super::OnLayout(e); -} - -LRESULT WGLElementalControl::WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) { - switch (message) { - case WM_PAINT: { - invalidated_ = false; - ValidateRect(hWnd, nullptr); - SCOPE_profile_cpu_i("gpu", "xe::ui::gl::WGLElementalControl::WM_PAINT"); - { - GLContextLock context_lock(&context_); - - float clear_color[] = {rand() / (float)RAND_MAX, 1.0f, 0, 1.0f}; - glClearNamedFramebufferfv(0, GL_COLOR, 0, clear_color); - - if (current_paint_callback_) { - current_paint_callback_(); - current_paint_callback_ = nullptr; - } - - UIEvent e(this); - OnPaint(e); - - // TODO(benvanik): profiler present. - Profiler::Present(); - } - { - SCOPE_profile_cpu_i("gpu", - "xe::ui::gl::WGLElementalControl::SwapBuffers"); - SwapBuffers(context_.dc()); - } - return 0; - } break; - } - return Win32Control::WndProc(hWnd, message, wParam, lParam); -} - -void WGLElementalControl::SynchronousRepaint( - std::function paint_callback) { - SCOPE_profile_cpu_f("gpu"); - - // We may already have a pending paint from a previous request when we - // were minimized. We just overwrite it. - current_paint_callback_ = std::move(paint_callback); - - // This will not return until the WM_PAINT has completed. - // Note, if we are minimized this won't do anything. - RedrawWindow(hwnd(), nullptr, nullptr, - RDW_INTERNALPAINT | RDW_UPDATENOW | RDW_ALLCHILDREN); -} - -} // namespace gl -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/gl/wgl_elemental_control.h b/src/xenia/ui/gl/wgl_elemental_control.h deleted file mode 100644 index deb0627e7..000000000 --- a/src/xenia/ui/gl/wgl_elemental_control.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ -#define XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ - -#include - -#include "xenia/base/threading.h" -#include "xenia/ui/elemental_control.h" -#include "xenia/ui/gl/gl_context.h" -#include "xenia/ui/loop.h" - -namespace xe { -namespace ui { -namespace gl { - -class WGLElementalControl : public ElementalControl { - public: - WGLElementalControl(Loop* loop); - ~WGLElementalControl() override; - - GLContext* context() { return &context_; } - - void SynchronousRepaint(std::function paint_callback); - - protected: - using super = ElementalControl; - - std::unique_ptr CreateRenderer() override; - - bool Create() override; - - void OnLayout(UIEvent& e) override; - - LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) override; - - private: - Loop* loop_; - GLContext context_; - std::function current_paint_callback_; -}; - -} // namespace gl -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_GL_WGL_ELEMENTAL_CONTROL_H_ diff --git a/src/xenia/ui/win32/win32_file_picker.h b/src/xenia/ui/graphics_context.cc similarity index 59% rename from src/xenia/ui/win32/win32_file_picker.h rename to src/xenia/ui/graphics_context.cc index ade3c2ba2..99fe9d91f 100644 --- a/src/xenia/ui/win32/win32_file_picker.h +++ b/src/xenia/ui/graphics_context.cc @@ -7,27 +7,15 @@ ****************************************************************************** */ -#ifndef XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ -#define XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ - -#include "xenia/ui/file_picker.h" +#include "xenia/ui/graphics_context.h" namespace xe { namespace ui { -namespace win32 { -class Win32FilePicker : public FilePicker { - public: - Win32FilePicker(); - ~Win32FilePicker() override; +GraphicsContext::GraphicsContext(Window* target_window) + : target_window_(target_window) {} - bool Show(void* parent_window_handle) override; +GraphicsContext::~GraphicsContext() = default; - private: -}; - -} // namespace win32 } // namespace ui } // namespace xe - -#endif // XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ diff --git a/src/xenia/ui/graphics_context.h b/src/xenia/ui/graphics_context.h new file mode 100644 index 000000000..5f2cfc919 --- /dev/null +++ b/src/xenia/ui/graphics_context.h @@ -0,0 +1,58 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_GRAPHICS_CONTEXT_H_ +#define XENIA_UI_GRAPHICS_CONTEXT_H_ + +#include + +#include "el/graphics/renderer.h" +#include "xenia/profiling.h" + +namespace xe { +namespace ui { + +class Window; + +class GraphicsContext { + public: + virtual ~GraphicsContext(); + + Window* target_window() const { return target_window_; } + + virtual std::unique_ptr CreateShared() = 0; + virtual std::unique_ptr CreateProfilerDisplay() = 0; + virtual std::unique_ptr CreateElementalRenderer() = 0; + + virtual bool MakeCurrent() = 0; + virtual void ClearCurrent() = 0; + + virtual void BeginSwap() = 0; + virtual void EndSwap() = 0; + + protected: + explicit GraphicsContext(Window* target_window); + + Window* target_window_ = nullptr; +}; + +struct GraphicsContextLock { + GraphicsContextLock(GraphicsContext* context) : context_(context) { + context_->MakeCurrent(); + } + ~GraphicsContextLock() { context_->ClearCurrent(); } + + private: + GraphicsContext* context_; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_GRAPHICS_CONTEXT_H_ diff --git a/src/xenia/ui/loop.cc b/src/xenia/ui/loop.cc new file mode 100644 index 000000000..aacb98955 --- /dev/null +++ b/src/xenia/ui/loop.cc @@ -0,0 +1,82 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2014 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/loop.h" + +#include "el/message_handler.h" +#include "el/util/metrics.h" +#include "el/util/timer.h" +#include "xenia/base/assert.h" +#include "xenia/base/threading.h" + +namespace xe { +namespace ui { + +// The loop designated as being the main loop elemental-forms will use for all +// activity. +// TODO(benvanik): refactor elemental-forms to now need a global. +Loop* elemental_loop_ = nullptr; + +Loop* Loop::elemental_loop() { return elemental_loop_; } + +void Loop::set_elemental_loop(Loop* loop) { + assert_null(elemental_loop_); + elemental_loop_ = loop; +} + +Loop::Loop() = default; + +Loop::~Loop() { + if (this == elemental_loop_) { + elemental_loop_ = nullptr; + } +} + +void Loop::PostSynchronous(std::function fn) { + xe::threading::Fence fence; + Post([&fn, &fence]() { + fn(); + fence.Signal(); + }); + fence.Wait(); +} + +} // namespace ui +} // namespace xe + +void el::util::RescheduleTimer(uint64_t fire_time) { + assert_not_null(xe::ui::elemental_loop_); + if (fire_time == el::MessageHandler::kNotSoon) { + return; + } + + uint64_t now = el::util::GetTimeMS(); + uint64_t delay_millis = fire_time >= now ? fire_time - now : 0; + xe::ui::elemental_loop_->PostDelayed([]() { + uint64_t next_fire_time = el::MessageHandler::GetNextMessageFireTime(); + uint64_t now = el::util::GetTimeMS(); + if (now < next_fire_time) { + // We timed out *before* we were supposed to (the OS is not playing + // nice). + // Calling ProcessMessages now won't achieve a thing so force a + // reschedule + // of the platform timer again with the same time. + // ReschedulePlatformTimer(next_fire_time, true); + return; + } + + el::MessageHandler::ProcessMessages(); + + // If we still have things to do (because we didn't process all + // messages, + // or because there are new messages), we need to rescedule, so call + // RescheduleTimer. + el::util::RescheduleTimer(el::MessageHandler::GetNextMessageFireTime()); + }, delay_millis); +} diff --git a/src/xenia/ui/loop.h b/src/xenia/ui/loop.h index 272202c34..4310e1330 100644 --- a/src/xenia/ui/loop.h +++ b/src/xenia/ui/loop.h @@ -11,20 +11,34 @@ #define XENIA_UI_LOOP_H_ #include +#include + +#include "xenia/base/delegate.h" +#include "xenia/ui/ui_event.h" namespace xe { namespace ui { class Loop { public: - Loop() = default; - virtual ~Loop() = default; + static std::unique_ptr Create(); + + static Loop* elemental_loop(); + // Sets the loop designated as being the main loop elemental-forms will use + // for all activity. + static void set_elemental_loop(Loop* loop); + + Loop(); + virtual ~Loop(); virtual void Post(std::function fn) = 0; virtual void PostDelayed(std::function fn, uint64_t delay_millis) = 0; + void PostSynchronous(std::function fn); virtual void Quit() = 0; virtual void AwaitQuit() = 0; + + Delegate on_quit; }; } // namespace ui diff --git a/src/xenia/ui/win32/win32_loop.cc b/src/xenia/ui/loop_win.cc similarity index 96% rename from src/xenia/ui/win32/win32_loop.cc rename to src/xenia/ui/loop_win.cc index 6ae28b368..0ce1a59d2 100644 --- a/src/xenia/ui/win32/win32_loop.cc +++ b/src/xenia/ui/loop_win.cc @@ -7,13 +7,12 @@ ****************************************************************************** */ -#include "xenia/ui/win32/win32_loop.h" +#include "xenia/ui/loop_win.h" #include "xenia/base/assert.h" namespace xe { namespace ui { -namespace win32 { const DWORD kWmWin32LoopPost = WM_APP + 0x100; const DWORD kWmWin32LoopQuit = WM_APP + 0x101; @@ -27,6 +26,8 @@ class PostedFn { std::function fn_; }; +std::unique_ptr Loop::Create() { return std::make_unique(); } + Win32Loop::Win32Loop() : thread_id_(0) { timer_queue_ = CreateTimerQueue(); @@ -81,6 +82,9 @@ void Win32Loop::ThreadMain() { break; } } + + UIEvent e(nullptr); + on_quit(e); } void Win32Loop::Post(std::function fn) { @@ -132,6 +136,5 @@ void Win32Loop::Quit() { void Win32Loop::AwaitQuit() { quit_fence_.Wait(); } -} // namespace win32 } // namespace ui } // namespace xe diff --git a/src/xenia/ui/win32/win32_loop.h b/src/xenia/ui/loop_win.h similarity index 90% rename from src/xenia/ui/win32/win32_loop.h rename to src/xenia/ui/loop_win.h index e0abb5913..c9732378f 100644 --- a/src/xenia/ui/win32/win32_loop.h +++ b/src/xenia/ui/loop_win.h @@ -7,8 +7,8 @@ ****************************************************************************** */ -#ifndef XENIA_UI_WIN32_WIN32_LOOP_H_ -#define XENIA_UI_WIN32_WIN32_LOOP_H_ +#ifndef XENIA_UI_LOOP_WIN_H_ +#define XENIA_UI_LOOP_WIN_H_ #include #include @@ -21,7 +21,6 @@ namespace xe { namespace ui { -namespace win32 { class Win32Loop : public Loop { public: @@ -55,8 +54,7 @@ class Win32Loop : public Loop { std::list pending_timers_; }; -} // namespace win32 } // namespace ui } // namespace xe -#endif // XENIA_UI_WIN32_WIN32_LOOP_H_ +#endif // XENIA_UI_LOOP_WIN_H_ diff --git a/src/xenia/ui/main_resources.rc b/src/xenia/ui/main_resources.rc new file mode 100644 index 000000000..5db04ad85 --- /dev/null +++ b/src/xenia/ui/main_resources.rc @@ -0,0 +1,5 @@ +//{{NO_DEPENDENCIES}} + +#include "third_party\\elemental-forms\\resources.rc" + +//IDR_xe_ui_main_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png" diff --git a/src/xenia/ui/main_window.cc b/src/xenia/ui/main_window.cc deleted file mode 100644 index 95be90a2a..000000000 --- a/src/xenia/ui/main_window.cc +++ /dev/null @@ -1,264 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/main_window.h" - -#include "xenia/base/clock.h" -#include "xenia/base/logging.h" -#include "xenia/base/platform.h" -#include "xenia/base/threading.h" -#include "xenia/gpu/graphics_system.h" -#include "xenia/emulator.h" -#include "xenia/profiling.h" - -namespace xe { -namespace ui { - -enum Commands { - IDC_FILE_EXIT, - - IDC_CPU_TIME_SCALAR_RESET, - IDC_CPU_TIME_SCALAR_HALF, - IDC_CPU_TIME_SCALAR_DOUBLE, - - IDC_CPU_PROFILER_TOGGLE_DISPLAY, - IDC_CPU_PROFILER_TOGGLE_PAUSE, - - IDC_GPU_TRACE_FRAME, - IDC_GPU_CLEAR_CACHES, - - IDC_WINDOW_FULLSCREEN, - - IDC_HELP_WEBSITE, - IDC_HELP_ABOUT, -}; - -const std::wstring kBaseTitle = L"xenia"; - -MainWindow::MainWindow(Emulator* emulator) - : PlatformWindow(&loop_, kBaseTitle), - emulator_(emulator), - main_menu_(MenuItem::Type::kNormal) {} - -MainWindow::~MainWindow() = default; - -std::unique_ptr MainWindow::Create(Emulator* emulator) { - std::unique_ptr main_window(new MainWindow(emulator)); - - xe::threading::Fence fence; - - main_window->loop()->Post([&main_window, &fence]() { - xe::threading::set_name("Win32 Loop"); - xe::Profiler::ThreadEnter("Win32 Loop"); - - if (!main_window->Initialize()) { - XEFATAL("Failed to initialize main window"); - exit(1); - } - - fence.Signal(); - }); - - fence.Wait(); - - return std::unique_ptr(main_window.release()); -} - -bool MainWindow::Initialize() { - if (!PlatformWindow::Initialize()) { - return false; - } - - UpdateTitle(); - on_key_down.AddListener([this](KeyEvent& e) { - bool handled = true; - switch (e.key_code()) { - case 0x0D: { // numpad enter - OnCommand(Commands::IDC_CPU_TIME_SCALAR_RESET); - } break; - case 0x6D: { // numpad minus - OnCommand(Commands::IDC_CPU_TIME_SCALAR_HALF); - } break; - case 0x6B: { // numpad plus - OnCommand(Commands::IDC_CPU_TIME_SCALAR_DOUBLE); - } break; - - case 0x73: { // VK_F4 - OnCommand(Commands::IDC_GPU_TRACE_FRAME); - } break; - case 0x74: { // VK_F5 - OnCommand(Commands::IDC_GPU_CLEAR_CACHES); - } break; - - case 0x7A: { // VK_F11 - OnCommand(Commands::IDC_WINDOW_FULLSCREEN); - } break; - case 0x1B: { // VK_ESCAPE - // Allow users to escape fullscreen (but not enter it). - if (is_fullscreen()) { - ToggleFullscreen(false); - } - } break; - - case 0x70: { // VK_F1 - OnCommand(Commands::IDC_HELP_WEBSITE); - } break; - - default: { handled = false; } break; - } - e.set_handled(handled); - }); - - // Main menu. - // FIXME: This code is really messy. - auto file_menu = - std::make_unique(MenuItem::Type::kPopup, L"&File"); - { - file_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_FILE_EXIT, L"E&xit", L"Alt+F4")); - } - main_menu_.AddChild(std::move(file_menu)); - - // CPU menu. - auto cpu_menu = - std::make_unique(MenuItem::Type::kPopup, L"&CPU"); - { - cpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_RESET, - L"&Reset Time Scalar", L"Numpad Enter")); - cpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_HALF, - L"Time Scalar /= 2", L"Numpad -")); - cpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_CPU_TIME_SCALAR_DOUBLE, - L"Time Scalar *= 2", L"Numpad +")); - } - cpu_menu->AddChild( - std::make_unique(MenuItem::Type::kSeparator)); - { - cpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_CPU_PROFILER_TOGGLE_DISPLAY, - L"Toggle Profiler &Display", L"Tab")); - cpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_CPU_PROFILER_TOGGLE_PAUSE, - L"&Pause/Resume Profiler", L"`")); - } - main_menu_.AddChild(std::move(cpu_menu)); - - // GPU menu. - auto gpu_menu = - std::make_unique(MenuItem::Type::kPopup, L"&GPU"); - { - gpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_GPU_TRACE_FRAME, L"&Trace Frame", - L"F4")); - } - gpu_menu->AddChild( - std::make_unique(MenuItem::Type::kSeparator)); - { - gpu_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_GPU_CLEAR_CACHES, - L"&Clear Caches", L"F5")); - } - main_menu_.AddChild(std::move(gpu_menu)); - - // Window menu. - auto window_menu = - std::make_unique(MenuItem::Type::kPopup, L"&Window"); - { - window_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_WINDOW_FULLSCREEN, - L"&Fullscreen", L"F11")); - } - main_menu_.AddChild(std::move(window_menu)); - - // Help menu. - auto help_menu = - std::make_unique(MenuItem::Type::kPopup, L"&Help"); - { - help_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_HELP_WEBSITE, L"&Website...", - L"F1")); - help_menu->AddChild(std::make_unique( - MenuItem::Type::kString, Commands::IDC_HELP_ABOUT, L"&About...")); - } - main_menu_.AddChild(std::move(help_menu)); - - set_menu(&main_menu_); - - Resize(1280, 720); - - return true; -} - -void MainWindow::UpdateTitle() { - std::wstring title(kBaseTitle); - if (Clock::guest_time_scalar() != 1.0) { - title += L" (@"; - title += xe::to_wstring(std::to_string(Clock::guest_time_scalar())); - title += L"x)"; - } - set_title(title); -} - -void MainWindow::OnClose() { - loop_.Quit(); - - // TODO(benvanik): proper exit. - XELOGI("User-initiated death!"); - exit(1); -} - -void MainWindow::OnCommand(int id) { - switch (id) { - case IDC_FILE_EXIT: { - Close(); - } break; - - case IDC_CPU_TIME_SCALAR_RESET: { - Clock::set_guest_time_scalar(1.0); - UpdateTitle(); - } break; - case IDC_CPU_TIME_SCALAR_HALF: { - Clock::set_guest_time_scalar(Clock::guest_time_scalar() / 2.0); - UpdateTitle(); - } break; - case IDC_CPU_TIME_SCALAR_DOUBLE: { - Clock::set_guest_time_scalar(Clock::guest_time_scalar() * 2.0); - UpdateTitle(); - } break; - case IDC_CPU_PROFILER_TOGGLE_DISPLAY: { - Profiler::ToggleDisplay(); - } break; - case IDC_CPU_PROFILER_TOGGLE_PAUSE: { - Profiler::TogglePause(); - } break; - - case IDC_GPU_TRACE_FRAME: { - emulator()->graphics_system()->RequestFrameTrace(); - } break; - case IDC_GPU_CLEAR_CACHES: { - emulator()->graphics_system()->ClearCaches(); - } break; - - case IDC_WINDOW_FULLSCREEN: { - ToggleFullscreen(!is_fullscreen()); - } break; - - case IDC_HELP_WEBSITE: { - LaunchBrowser("http://xenia.jp"); - } break; - case IDC_HELP_ABOUT: { - LaunchBrowser("http://xenia.jp/about/"); - } break; - } -} - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/main_window.h b/src/xenia/ui/main_window.h deleted file mode 100644 index 3f5a9bbcc..000000000 --- a/src/xenia/ui/main_window.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_MAIN_WINDOW_H_ -#define XENIA_UI_MAIN_WINDOW_H_ - -#include "xenia/ui/platform.h" -#include "xenia/ui/window.h" -#include "xenia/xbox.h" - -namespace xe { -class Emulator; -} // namespace xe - -namespace xe { -namespace ui { - -class MainWindow : public PlatformWindow { - public: - ~MainWindow() override; - - static std::unique_ptr Create(Emulator* emulator); - - Emulator* emulator() const { return emulator_; } - - private: - explicit MainWindow(Emulator* emulator); - - bool Initialize(); - - void UpdateTitle(); - - void OnClose() override; - void OnCommand(int id) override; - - Emulator* emulator_; - PlatformLoop loop_; - PlatformMenu main_menu_; -}; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_MAIN_WINDOW_H_ diff --git a/src/xenia/ui/menu_item.cc b/src/xenia/ui/menu_item.cc index e77ebae61..06f8c9774 100644 --- a/src/xenia/ui/menu_item.cc +++ b/src/xenia/ui/menu_item.cc @@ -12,11 +12,27 @@ namespace xe { namespace ui { -MenuItem::MenuItem(Type type) : type_(type), parent_item_(nullptr) {} +std::unique_ptr MenuItem::Create(Type type) { + return MenuItem::Create(type, L"", L"", nullptr); +} + +std::unique_ptr MenuItem::Create(Type type, + const std::wstring& text) { + return MenuItem::Create(type, text, L"", nullptr); +} + +std::unique_ptr MenuItem::Create(Type type, const std::wstring& text, + std::function callback) { + return MenuItem::Create(type, text, L"", std::move(callback)); +} MenuItem::MenuItem(Type type, const std::wstring& text, - const std::wstring& hotkey) - : type_(type), parent_item_(nullptr), text_(text), hotkey_(hotkey) {} + const std::wstring& hotkey, std::function callback) + : type_(type), + parent_item_(nullptr), + text_(text), + hotkey_(hotkey), + callback_(std::move(callback)) {} MenuItem::~MenuItem() = default; @@ -45,7 +61,13 @@ void MenuItem::RemoveChild(MenuItem* child_item) { } } -void MenuItem::OnSelected(UIEvent& e) { on_selected(e); } +MenuItem* MenuItem::child(size_t index) { return children_[index].get(); } + +void MenuItem::OnSelected(UIEvent& e) { + if (callback_) { + callback_(); + } +} } // namespace ui } // namespace xe diff --git a/src/xenia/ui/menu_item.h b/src/xenia/ui/menu_item.h index d21c70d9f..5df2c5e00 100644 --- a/src/xenia/ui/menu_item.h +++ b/src/xenia/ui/menu_item.h @@ -10,10 +10,10 @@ #ifndef XENIA_UI_MENU_ITEM_H_ #define XENIA_UI_MENU_ITEM_H_ +#include #include #include -#include "xenia/base/delegate.h" #include "xenia/ui/ui_event.h" namespace xe { @@ -30,6 +30,14 @@ class MenuItem { kString, // Menu is just a string }; + static std::unique_ptr Create(Type type); + static std::unique_ptr Create(Type type, const std::wstring& text); + static std::unique_ptr Create(Type type, const std::wstring& text, + std::function callback); + static std::unique_ptr Create(Type type, const std::wstring& text, + const std::wstring& hotkey, + std::function callback); + virtual ~MenuItem(); MenuItem* parent_item() const { return parent_item_; } @@ -41,12 +49,11 @@ class MenuItem { void AddChild(std::unique_ptr child_item); void AddChild(MenuItemPtr child_item); void RemoveChild(MenuItem* child_item); - - Delegate on_selected; + MenuItem* child(size_t index); protected: - MenuItem(Type type); - MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey); + MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey, + std::function callback); virtual void OnChildAdded(MenuItem* child_item) {} virtual void OnChildRemoved(MenuItem* child_item) {} @@ -58,6 +65,7 @@ class MenuItem { std::vector children_; std::wstring text_; std::wstring hotkey_; + std::function callback_; }; } // namespace ui diff --git a/src/xenia/ui/platform.h b/src/xenia/ui/platform.h deleted file mode 100644 index 6aaf04811..000000000 --- a/src/xenia/ui/platform.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -/** -****************************************************************************** -* Xenia : Xbox 360 Emulator Research Project * -****************************************************************************** -* Copyright 2014 Ben Vanik. All rights reserved. * -* Released under the BSD license - see LICENSE in the root for more details. * -****************************************************************************** -*/ - -#ifndef XENIA_UI_PLATFORM_H_ -#define XENIA_UI_PLATFORM_H_ - -// TODO(benvanik): only on windows. -#include "xenia/ui/win32/win32_control.h" -#include "xenia/ui/win32/win32_file_picker.h" -#include "xenia/ui/win32/win32_loop.h" -#include "xenia/ui/win32/win32_menu_item.h" -#include "xenia/ui/win32/win32_window.h" - -namespace xe { -namespace ui { - -using PlatformControl = xe::ui::win32::Win32Control; -using PlatformFilePicker = xe::ui::win32::Win32FilePicker; -using PlatformLoop = xe::ui::win32::Win32Loop; -using PlatformMenu = xe::ui::win32::Win32MenuItem; -using PlatformWindow = xe::ui::win32::Win32Window; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_PLATFORM_H_ diff --git a/src/xenia/ui/ui_event.h b/src/xenia/ui/ui_event.h index 706dd7778..e1a46be6b 100644 --- a/src/xenia/ui/ui_event.h +++ b/src/xenia/ui/ui_event.h @@ -13,23 +13,23 @@ namespace xe { namespace ui { -class Control; +class Window; class UIEvent { public: - UIEvent(Control* control = nullptr) : control_(control) {} + UIEvent(Window* target = nullptr) : target_(target) {} virtual ~UIEvent() = default; - Control* control() const { return control_; } + Window* target() const { return target_; } private: - Control* control_; + Window* target_ = nullptr; }; class KeyEvent : public UIEvent { public: - KeyEvent(Control* control, int key_code) - : UIEvent(control), handled_(false), key_code_(key_code) {} + KeyEvent(Window* target, int key_code) + : UIEvent(target), key_code_(key_code) {} ~KeyEvent() override = default; bool is_handled() const { return handled_; } @@ -38,8 +38,8 @@ class KeyEvent : public UIEvent { int key_code() const { return key_code_; } private: - bool handled_; - int key_code_; + bool handled_ = false; + int key_code_ = 0; }; class MouseEvent : public UIEvent { @@ -54,15 +54,9 @@ class MouseEvent : public UIEvent { }; public: - MouseEvent(Control* control, Button button, int32_t x, int32_t y, + MouseEvent(Window* target, Button button, int32_t x, int32_t y, int32_t dx = 0, int32_t dy = 0) - : UIEvent(control), - handled_(false), - button_(button), - x_(x), - y_(y), - dx_(dx), - dy_(dy) {} + : UIEvent(target), button_(button), x_(x), y_(y), dx_(dx), dy_(dy) {} ~MouseEvent() override = default; bool is_handled() const { return handled_; } @@ -75,12 +69,12 @@ class MouseEvent : public UIEvent { int32_t dy() const { return dy_; } private: - bool handled_; + bool handled_ = false; Button button_; - int32_t x_; - int32_t y_; - int32_t dx_; - int32_t dy_; + int32_t x_ = 0; + int32_t y_ = 0; + int32_t dx_ = 0; + int32_t dy_ = 0; }; } // namespace ui diff --git a/src/xenia/ui/win32/win32_control.cc b/src/xenia/ui/win32/win32_control.cc deleted file mode 100644 index 776c9aa96..000000000 --- a/src/xenia/ui/win32/win32_control.cc +++ /dev/null @@ -1,379 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/win32/win32_control.h" - -namespace xe { -namespace ui { -namespace win32 { - -Win32Control::Win32Control(uint32_t flags) : Control(flags), hwnd_(nullptr) {} - -Win32Control::~Win32Control() { - if (hwnd_) { - SetWindowLongPtr(hwnd_, GWLP_USERDATA, 0); - CloseWindow(hwnd_); - hwnd_ = nullptr; - } -} - -void Win32Control::OnCreate() { - Control::OnCreate(); - - // Create all children, if needed. - for (auto& child_control : children_) { - auto win32_control = static_cast(child_control.get()); - if (!win32_control->hwnd()) { - win32_control->Create(); - } else { - SetParent(win32_control->hwnd(), hwnd()); - } - win32_control->Layout(); - } - - if (!is_cursor_visible_) { - ShowCursor(FALSE); - } - if (!is_enabled_) { - EnableWindow(hwnd_, false); - } - if (!is_visible_) { - ShowWindow(hwnd_, SW_HIDE); - } - if (has_focus_) { - SetFocus(hwnd_); - } -} - -void Win32Control::OnDestroy() { - // Destroy all children, if needed. - for (auto& child_control : children_) { - auto win32_control = static_cast(child_control.get()); - if (!win32_control->hwnd()) { - win32_control->Destroy(); - } - } -} - -void Win32Control::OnChildAdded(Control* child_control) { - auto win32_control = static_cast(child_control); - if (hwnd_) { - if (!win32_control->hwnd()) { - win32_control->Create(); - } else { - SetParent(win32_control->hwnd(), hwnd()); - } - child_control->Layout(); - } -} - -void Win32Control::OnChildRemoved(Control* child_control) { - auto win32_control = static_cast(child_control); - if (win32_control->hwnd()) { - SetParent(win32_control->hwnd(), nullptr); - } -} - -void Win32Control::Resize(int32_t width, int32_t height) { - MoveWindow(hwnd_, 0, 0, width, height, TRUE); -} - -void Win32Control::Resize(int32_t left, int32_t top, int32_t right, - int32_t bottom) { - MoveWindow(hwnd_, left, top, right - left, bottom - top, TRUE); -} - -void Win32Control::ResizeToFill(int32_t pad_left, int32_t pad_top, - int32_t pad_right, int32_t pad_bottom) { - if (!parent_) { - // TODO(benvanik): screen? - return; - } - RECT parent_rect; - auto parent_control = static_cast(parent_); - GetClientRect(parent_control->hwnd(), &parent_rect); - MoveWindow(hwnd_, parent_rect.left + pad_left, parent_rect.top + pad_top, - parent_rect.right - pad_right - pad_left, - parent_rect.bottom - pad_bottom - pad_top, TRUE); -} - -void Win32Control::OnResize(UIEvent& e) { - RECT client_rect; - GetClientRect(hwnd_, &client_rect); - int32_t width = client_rect.right - client_rect.left; - int32_t height = client_rect.bottom - client_rect.top; - if (width != width_ || height != height_) { - width_ = width; - height_ = height; - Layout(); - for (auto& child_control : children_) { - auto win32_control = static_cast(child_control.get()); - win32_control->OnResize(e); - win32_control->Invalidate(); - } - } -} - -void Win32Control::Invalidate() { - if (invalidated_) { - return; - } - invalidated_ = true; - InvalidateRect(hwnd_, nullptr, FALSE); - for (auto& child_control : children_) { - child_control->Invalidate(); - } -} - -void Win32Control::set_cursor_visible(bool value) { - if (is_cursor_visible_ == value) { - return; - } - if (value) { - ShowCursor(TRUE); - SetCursor(nullptr); - } else { - ShowCursor(FALSE); - } -} - -void Win32Control::set_enabled(bool value) { - if (is_enabled_ == value) { - return; - } - if (hwnd_) { - EnableWindow(hwnd_, value); - } else { - is_enabled_ = value; - } -} - -void Win32Control::set_visible(bool value) { - if (is_visible_ == value) { - return; - } - if (hwnd_) { - ShowWindow(hwnd_, value ? SW_SHOWNOACTIVATE : SW_HIDE); - } else { - is_visible_ = value; - } -} - -void Win32Control::set_focus(bool value) { - if (has_focus_ == value) { - return; - } - if (hwnd_) { - if (value) { - SetFocus(hwnd_); - } else { - SetFocus(nullptr); - } - } else { - has_focus_ = value; - } -} - -LRESULT CALLBACK Win32Control::WndProcThunk(HWND hWnd, UINT message, - WPARAM wParam, LPARAM lParam) { - Win32Control* control = nullptr; - if (message == WM_NCCREATE) { - auto create_struct = reinterpret_cast(lParam); - control = reinterpret_cast(create_struct->lpCreateParams); - SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(LONG_PTR)control); - } else { - control = - reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); - } - if (control) { - return control->WndProc(hWnd, message, wParam, lParam); - } else { - return DefWindowProc(hWnd, message, wParam, lParam); - } -} - -LRESULT Win32Control::WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) { - if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) { - if (HandleMouse(message, wParam, lParam)) { - SetFocus(hwnd_); - return 0; - } else { - return DefWindowProc(hWnd, message, wParam, lParam); - } - } else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) { - if (HandleKeyboard(message, wParam, lParam)) { - SetFocus(hwnd_); - return 0; - } else { - return DefWindowProc(hWnd, message, wParam, lParam); - } - } - - switch (message) { - case WM_NCCREATE: - break; - case WM_CREATE: - break; - case WM_DESTROY: - OnDestroy(); - break; - - case WM_MOVING: - break; - case WM_MOVE: - break; - case WM_SIZING: - break; - case WM_SIZE: { - auto e = UIEvent(this); - OnResize(e); - break; - } - - case WM_PAINT: { - if (flags_ & kFlagOwnPaint) { - return 0; // ignore - } - invalidated_ = false; - auto e = UIEvent(this); - OnPaint(e); - break; - } - case WM_ERASEBKGND: - if (flags_ & kFlagOwnPaint) { - return 0; // ignore - } - break; - case WM_DISPLAYCHANGE: - break; - - case WM_SHOWWINDOW: { - if (wParam == TRUE) { - auto e = UIEvent(this); - OnVisible(e); - } else { - auto e = UIEvent(this); - OnHidden(e); - } - break; - } - - case WM_KILLFOCUS: { - has_focus_ = false; - auto e = UIEvent(this); - OnLostFocus(e); - break; - } - case WM_SETFOCUS: { - has_focus_ = true; - auto e = UIEvent(this); - OnGotFocus(e); - break; - } - } - - return DefWindowProc(hWnd, message, wParam, lParam); -} - -bool Win32Control::HandleMouse(UINT message, WPARAM wParam, LPARAM lParam) { - int32_t x = GET_X_LPARAM(lParam); - int32_t y = GET_Y_LPARAM(lParam); - if (message == WM_MOUSEWHEEL) { - POINT pt = {x, y}; - ScreenToClient(hwnd_, &pt); - x = pt.x; - y = pt.y; - } - - MouseEvent::Button button = MouseEvent::Button::kNone; - int32_t dx = 0; - int32_t dy = 0; - switch (message) { - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - button = MouseEvent::Button::kLeft; - break; - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - button = MouseEvent::Button::kRight; - break; - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - button = MouseEvent::Button::kMiddle; - break; - case WM_XBUTTONDOWN: - case WM_XBUTTONUP: - switch (GET_XBUTTON_WPARAM(wParam)) { - case XBUTTON1: - button = MouseEvent::Button::kX1; - break; - case XBUTTON2: - button = MouseEvent::Button::kX2; - break; - default: - return false; - } - break; - case WM_MOUSEMOVE: - button = MouseEvent::Button::kNone; - break; - case WM_MOUSEWHEEL: - button = MouseEvent::Button::kNone; - dx = 0; // ? - dy = GET_WHEEL_DELTA_WPARAM(wParam); - break; - default: - // Double click/etc? - return true; - } - - auto e = MouseEvent(this, button, x, y, dx, dy); - switch (message) { - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_XBUTTONDOWN: - OnMouseDown(e); - break; - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: - OnMouseUp(e); - break; - case WM_MOUSEMOVE: - OnMouseMove(e); - break; - case WM_MOUSEWHEEL: - OnMouseWheel(e); - break; - } - return e.is_handled(); -} - -bool Win32Control::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam) { - auto e = KeyEvent(this, (int)wParam); - switch (message) { - case WM_KEYDOWN: - OnKeyDown(e); - break; - case WM_KEYUP: - OnKeyUp(e); - break; - case WM_CHAR: - OnKeyChar(e); - break; - } - return e.is_handled(); -} - -} // namespace win32 -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/win32/win32_menu_item.cc b/src/xenia/ui/win32/win32_menu_item.cc deleted file mode 100644 index 69993abc8..000000000 --- a/src/xenia/ui/win32/win32_menu_item.cc +++ /dev/null @@ -1,72 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/win32/win32_menu_item.h" - -#include "xenia/base/assert.h" - -namespace xe { -namespace ui { -namespace win32 { - -Win32MenuItem::Win32MenuItem(Type type, int id, const std::wstring& text, - const std::wstring& hotkey) - : id_(id), MenuItem(type, text, hotkey) { - switch (type) { - case MenuItem::Type::kNormal: - handle_ = CreateMenu(); - break; - case MenuItem::Type::kPopup: - handle_ = CreatePopupMenu(); - break; - } -} - -Win32MenuItem::Win32MenuItem(Type type) : Win32MenuItem(type, 0, L"") {} - -Win32MenuItem::Win32MenuItem(Type type, const std::wstring& text, - const std::wstring& hotkey) - : Win32MenuItem(type, 0, text, hotkey) {} - -Win32MenuItem::~Win32MenuItem() { - if (handle_) { - DestroyMenu(handle_); - } -} - -void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) { - auto child_item = static_cast(generic_child_item); - - switch (child_item->type()) { - case MenuItem::Type::kPopup: - AppendMenuW(handle_, MF_POPUP, - reinterpret_cast(child_item->handle()), - child_item->text().c_str()); - break; - case MenuItem::Type::kSeparator: - AppendMenuW(handle_, MF_SEPARATOR, child_item->id(), 0); - break; - case MenuItem::Type::kString: - auto full_name = child_item->text(); - if (!child_item->hotkey().empty()) { - full_name += L"\t" + child_item->hotkey(); - } - AppendMenuW(handle_, MF_STRING, child_item->id(), full_name.c_str()); - break; - } -} - -void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) { - auto child_item = static_cast(generic_child_item); - // -} - -} // namespace win32 -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/win32/win32_menu_item.h b/src/xenia/ui/win32/win32_menu_item.h deleted file mode 100644 index 46974229a..000000000 --- a/src/xenia/ui/win32/win32_menu_item.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_WIN32_WIN32_MENU_ITEM_H_ -#define XENIA_UI_WIN32_WIN32_MENU_ITEM_H_ - -#include "xenia/base/platform.h" -#include "xenia/ui/menu_item.h" - -namespace xe { -namespace ui { -namespace win32 { - -class Win32MenuItem : public MenuItem { - public: - Win32MenuItem(Type type); - Win32MenuItem(Type type, const std::wstring& text, - const std::wstring& hotkey = L""); - Win32MenuItem(Type type, int id, const std::wstring& text, - const std::wstring& hotkey = L""); - ~Win32MenuItem() override; - - HMENU handle() { return handle_; } - int id() { return id_; } - - protected: - void OnChildAdded(MenuItem* child_item) override; - void OnChildRemoved(MenuItem* child_item) override; - - private: - HMENU handle_; - uint32_t position_; // Position within parent, if any - int id_; -}; - -} // namespace win32 -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_WIN32_WIN32_MENU_ITEM_H_ diff --git a/src/xenia/ui/win32/win32_window.cc b/src/xenia/ui/win32/win32_window.cc deleted file mode 100644 index 4436e789d..000000000 --- a/src/xenia/ui/win32/win32_window.cc +++ /dev/null @@ -1,250 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/win32/win32_window.h" - -#include - -#include "xenia/base/logging.h" - -namespace xe { -namespace ui { -namespace win32 { - -Win32Window::Win32Window(Loop* loop, const std::wstring& title) - : Window(loop, title) {} - -Win32Window::~Win32Window() = default; - -bool Win32Window::Initialize() { - Create(); - return true; -} - -bool Win32Window::Create() { - HINSTANCE hInstance = GetModuleHandle(nullptr); - - WNDCLASSEX wcex; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wcex.lpfnWndProc = Win32Control::WndProcThunk; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1); - wcex.hIconSm = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1); - wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wcex.lpszMenuName = nullptr; - wcex.lpszClassName = L"XeniaWindowClass"; - if (!RegisterClassEx(&wcex)) { - XELOGE("RegisterClassEx failed"); - return false; - } - - // Setup initial size. - DWORD window_style = WS_OVERLAPPEDWINDOW; - DWORD window_ex_style = WS_EX_APPWINDOW | WS_EX_CONTROLPARENT; - RECT rc = {0, 0, width_, height_}; - AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); - - // Create window. - hwnd_ = CreateWindowEx(window_ex_style, L"XeniaWindowClass", title_.c_str(), - window_style, rc.left, rc.top, rc.right - rc.left, - rc.bottom - rc.top, nullptr, nullptr, hInstance, this); - if (!hwnd_) { - XELOGE("CreateWindow failed"); - return false; - } - - // Disable flicks. - ATOM atom = GlobalAddAtom(L"MicrosoftTabletPenServiceProperty"); - const DWORD_PTR dwHwndTabletProperty = - TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) - // gesture - TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves) - TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button - // down (circle) - TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, drag down, - // drag up) - TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING | - TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA; - SetProp(hwnd_, L"MicrosoftTabletPenServiceProperty", - reinterpret_cast(dwHwndTabletProperty)); - GlobalDeleteAtom(atom); - - // Enable DWM elevation. - EnableMMCSS(); - - ShowWindow(hwnd_, SW_SHOWNORMAL); - UpdateWindow(hwnd_); - - return 0; -} - -void Win32Window::EnableMMCSS() { - HMODULE hLibrary = LoadLibrary(L"DWMAPI.DLL"); - if (!hLibrary) { - return; - } - - typedef HRESULT(__stdcall * PDwmEnableMMCSS)(BOOL); - PDwmEnableMMCSS pDwmEnableMMCSS = - (PDwmEnableMMCSS)GetProcAddress(hLibrary, "DwmEnableMMCSS"); - if (pDwmEnableMMCSS) { - pDwmEnableMMCSS(TRUE); - } - - typedef HRESULT(__stdcall * PDwmSetPresentParameters)( - HWND, DWM_PRESENT_PARAMETERS*); - PDwmSetPresentParameters pDwmSetPresentParameters = - (PDwmSetPresentParameters)GetProcAddress(hLibrary, - "DwmSetPresentParameters"); - if (pDwmSetPresentParameters) { - DWM_PRESENT_PARAMETERS pp; - std::memset(&pp, 0, sizeof(DWM_PRESENT_PARAMETERS)); - pp.cbSize = sizeof(DWM_PRESENT_PARAMETERS); - pp.fQueue = FALSE; - pp.cBuffer = 2; - pp.fUseSourceRate = FALSE; - pp.cRefreshesPerFrame = 1; - pp.eSampling = DWM_SOURCE_FRAME_SAMPLING_POINT; - pDwmSetPresentParameters(hwnd_, &pp); - } - - FreeLibrary(hLibrary); -} - -bool Win32Window::set_title(const std::wstring& title) { - if (!Window::set_title(title)) { - return false; - } - SetWindowText(hwnd_, title.c_str()); - return true; -} - -void Win32Window::Resize(int32_t width, int32_t height) { - RECT rc = {0, 0, width, height}; - bool has_menu = menu_ ? true : false; - AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu); - if (true) { - rc.right += 100 - rc.left; - rc.left = 100; - rc.bottom += 100 - rc.top; - rc.top = 100; - } - Window::Resize(rc.left, rc.top, rc.right, rc.bottom); -} - -void Win32Window::Resize(int32_t left, int32_t top, int32_t right, - int32_t bottom) { - RECT rc = {left, top, right, bottom}; - bool has_menu = menu_ ? true : false; - AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu); - Window::Resize(rc.left, rc.top, rc.right, rc.bottom); -} - -void Win32Window::ResizeToFill(int32_t pad_left, int32_t pad_top, - int32_t pad_right, int32_t pad_bottom) { - // TODO(benvanik): fullscreen. -} - -bool Win32Window::is_fullscreen() const { - DWORD style = GetWindowLong(hwnd_, GWL_STYLE); - return (style & WS_OVERLAPPEDWINDOW) != WS_OVERLAPPEDWINDOW; -} - -void Win32Window::ToggleFullscreen(bool fullscreen) { - if (fullscreen == is_fullscreen()) { - return; - } - - DWORD style = GetWindowLong(hwnd_, GWL_STYLE); - if (fullscreen) { - // Kill our borders and resize to take up entire primary monitor - // http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx - MONITORINFO mi = {sizeof(mi)}; - if (GetWindowPlacement(hwnd_, &windowed_pos_) && - GetMonitorInfo(MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY), - &mi)) { - SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); - ::SetMenu(hwnd_, NULL); - - // Call into parent class to get around menu resizing code - Window::Resize(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, - mi.rcMonitor.bottom); - } - } else { - // Reinstate borders, resize to 1280x720 - SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); - SetWindowPlacement(hwnd_, &windowed_pos_); - - Win32MenuItem* win_menu = reinterpret_cast(menu_); - if (win_menu) { - ::SetMenu(hwnd_, win_menu->handle()); - } - } -} - -void Win32Window::OnClose() { - if (!closing_ && hwnd_) { - closing_ = true; - CloseWindow(hwnd_); - } -} - -void Win32Window::OnSetMenu(MenuItem* menu) { - Win32MenuItem* win_menu = reinterpret_cast(menu); - // Don't actually set the menu if we're fullscreen. We'll do that later. - if (win_menu && !is_fullscreen()) { - ::SetMenu(hwnd_, win_menu->handle()); - } -} - -LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) { - switch (message) { - case WM_ACTIVATEAPP: - if (wParam) { - // Made active. - OnShow(); - } else { - // Made inactive. - OnHide(); - } - break; - case WM_CLOSE: - closing_ = true; - Close(); - break; - - case WM_TABLET_QUERYSYSTEMGESTURESTATUS: - return TABLET_DISABLE_PRESSANDHOLD | // disables press and hold - // (right-click) gesture - TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up - // (waves) - TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen - // button down (circle) - TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, - // drag down, drag up) - TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING | - TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA; - - case WM_COMMAND: { - // TODO: Redirect this to MenuItem's on_selected delegate. - OnCommand(LOWORD(wParam)); - } break; - } - - return Win32Control::WndProc(hWnd, message, wParam, lParam); -} - -} // namespace win32 -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/win32/win32_window.h b/src/xenia/ui/win32/win32_window.h deleted file mode 100644 index b655e46c1..000000000 --- a/src/xenia/ui/win32/win32_window.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_WIN32_WIN32_WINDOW_H_ -#define XENIA_UI_WIN32_WIN32_WINDOW_H_ - -#include - -#include "xenia/ui/win32/win32_control.h" -#include "xenia/ui/win32/win32_menu_item.h" -#include "xenia/ui/window.h" - -namespace xe { -namespace ui { -namespace win32 { - -class Win32Window : public Window { - public: - Win32Window(Loop* loop, const std::wstring& title); - ~Win32Window() override; - - bool Initialize() override; - - bool set_title(const std::wstring& title) override; - - void Resize(int32_t width, int32_t height) override; - void Resize(int32_t left, int32_t top, int32_t right, - int32_t bottom) override; - void ResizeToFill(int32_t pad_left, int32_t pad_top, int32_t pad_right, - int32_t pad_bottom) override; - - bool is_fullscreen() const override; - void ToggleFullscreen(bool fullscreen) override; - - protected: - bool Create() override; - void OnClose() override; - - void OnSetMenu(MenuItem*) override; - - LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) override; - - private: - void EnableMMCSS(); - - bool closing_ = false; - - WINDOWPLACEMENT windowed_pos_; -}; - -} // namespace win32 -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_WIN32_WIN32_WINDOW_H_ diff --git a/src/xenia/ui/elemental_control.cc b/src/xenia/ui/window.cc similarity index 75% rename from src/xenia/ui/elemental_control.cc rename to src/xenia/ui/window.cc index 5a3251876..18118c621 100644 --- a/src/xenia/ui/elemental_control.cc +++ b/src/xenia/ui/window.cc @@ -2,12 +2,12 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2015 Ben Vanik. All rights reserved. * + * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ -#include "xenia/ui/elemental_control.h" +#include "xenia/ui/window.h" #include "el/animation_manager.h" #include "el/util/debug.h" @@ -28,6 +28,7 @@ namespace xe { namespace ui { constexpr bool kContinuousRepaint = false; +constexpr bool kShowPresentFps = kContinuousRepaint; // Enables long press behaviors (context menu, etc). constexpr bool kTouch = false; @@ -37,19 +38,26 @@ constexpr double kDoubleClickDistance = 5; constexpr int32_t kMouseWheelDetent = 120; -Loop* elemental_loop_ = nullptr; - class RootElement : public el::Element { public: - RootElement(ElementalControl* owner) : owner_(owner) {} + RootElement(Window* owner) : owner_(owner) {} void OnInvalid() override { owner_->Invalidate(); } private: - ElementalControl* owner_ = nullptr; + Window* owner_ = nullptr; }; -bool ElementalControl::InitializeElemental(Loop* loop, - el::graphics::Renderer* renderer) { +Window::Window(Loop* loop, const std::wstring& title) + : loop_(loop), title_(title) {} + +Window::~Window() { + // Context must have been cleaned up already in OnDestroy. + assert_null(context_.get()); +} + +bool Window::InitializeElemental(Loop* loop, el::graphics::Renderer* renderer) { + GraphicsContextLock context_lock(context_.get()); + static bool has_initialized = false; if (has_initialized) { return true; @@ -58,7 +66,7 @@ bool ElementalControl::InitializeElemental(Loop* loop, // Need to pass off the Loop we want Elemental to use. // TODO(benvanik): give the callback to elemental instead. - elemental_loop_ = loop; + Loop::set_elemental_loop(loop); if (!el::Initialize(renderer)) { XELOGE("Failed to initialize elemental core"); @@ -116,18 +124,10 @@ bool ElementalControl::InitializeElemental(Loop* loop, return true; } -ElementalControl::ElementalControl(Loop* loop, uint32_t flags) - : super(flags), loop_(loop) {} +bool Window::OnCreate() { return true; } -ElementalControl::~ElementalControl() = default; - -bool ElementalControl::Create() { - if (!super::Create()) { - return false; - } - - // Create subclass renderer (GL, etc). - renderer_ = CreateRenderer(); +bool Window::MakeReady() { + renderer_ = context_->CreateElementalRenderer(); // Initialize elemental. // TODO(benvanik): once? Do we care about multiple controls? @@ -139,26 +139,49 @@ bool ElementalControl::Create() { // TODO(benvanik): setup elements. root_element_ = std::make_unique(this); root_element_->set_background_skin(TBIDC("background")); - root_element_->set_rect({0, 0, 1000, 1000}); + root_element_->set_rect({0, 0, width(), height()}); + + // el::util::ShowDebugInfoSettingsWindow(root_element_.get()); return true; } -bool ElementalControl::LoadLanguage(std::string filename) { +void Window::OnMainMenuChange() {} + +void Window::OnClose() { + auto e = UIEvent(this); + on_closing(e); + on_closed(e); +} + +void Window::OnDestroy() { + el::Shutdown(); + + // Context must go last. + root_element_.reset(); + renderer_.reset(); + context_.reset(); +} + +bool Window::LoadLanguage(std::string filename) { return el::util::StringTable::get()->Load(filename.c_str()); } -bool ElementalControl::LoadSkin(std::string filename) { +bool Window::LoadSkin(std::string filename) { return el::Skin::get()->Load(filename.c_str()); } -void ElementalControl::Destroy() { - el::Shutdown(); - super::Destroy(); +void Window::Layout() { + auto e = UIEvent(this); + OnLayout(e); } -void ElementalControl::OnLayout(UIEvent& e) { - super::OnLayout(e); +void Window::Invalidate() {} + +void Window::OnResize(UIEvent& e) { on_resize(e); } + +void Window::OnLayout(UIEvent& e) { + on_layout(e); if (!root_element()) { return; } @@ -166,9 +189,8 @@ void ElementalControl::OnLayout(UIEvent& e) { root_element()->set_rect({0, 0, width(), height()}); } -void ElementalControl::OnPaint(UIEvent& e) { - super::OnPaint(e); - if (!root_element()) { +void Window::OnPaint(UIEvent& e) { + if (!renderer()) { return; } @@ -184,47 +206,74 @@ void ElementalControl::OnPaint(UIEvent& e) { // Update TB (run animations, handle deferred input, etc). el::AnimationManager::Update(); - root_element()->InvokeProcessStates(); - root_element()->InvokeProcess(); + if (root_element()) { + root_element()->InvokeProcessStates(); + root_element()->InvokeProcess(); + } + + GraphicsContextLock context_lock(context_.get()); + + context_->BeginSwap(); + + on_painting(e); renderer()->BeginPaint(width(), height()); // Render entire control hierarchy. - root_element()->InvokePaint(el::Element::PaintProps()); + if (root_element()) { + root_element()->InvokePaint(el::Element::PaintProps()); + } - // Render debug overlay. - root_element()->computed_font()->DrawString( - 5, 5, el::Color(255, 0, 0), - el::format_string("Frame %lld", frame_count_)); - if (kContinuousRepaint) { + on_paint(e); + + if (root_element() && kShowPresentFps) { + // Render debug overlay. root_element()->computed_font()->DrawString( - 5, 20, el::Color(255, 0, 0), el::format_string("FPS: %d", fps_)); + 5, 5, el::Color(255, 0, 0), + el::format_string("Frame %lld", frame_count_)); + root_element()->computed_font()->DrawString( + 5, 20, el::Color(255, 0, 0), + el::format_string("Present FPS: %d", fps_)); } renderer()->EndPaint(); + on_painted(e); + + context_->EndSwap(); + // If animations are running, reinvalidate immediately. - if (el::AnimationManager::has_running_animations()) { - root_element()->Invalidate(); - } - if (kContinuousRepaint) { - // Force an immediate repaint, always. - root_element()->Invalidate(); + if (root_element()) { + if (el::AnimationManager::has_running_animations()) { + root_element()->Invalidate(); + } + if (kContinuousRepaint) { + // Force an immediate repaint, always. + root_element()->Invalidate(); + } + } else { + if (kContinuousRepaint) { + Invalidate(); + } } } -void ElementalControl::OnGotFocus(UIEvent& e) { super::OnGotFocus(e); } +void Window::OnVisible(UIEvent& e) { on_visible(e); } -void ElementalControl::OnLostFocus(UIEvent& e) { - super::OnLostFocus(e); +void Window::OnHidden(UIEvent& e) { on_hidden(e); } + +void Window::OnGotFocus(UIEvent& e) { on_got_focus(e); } + +void Window::OnLostFocus(UIEvent& e) { modifier_shift_pressed_ = false; modifier_cntrl_pressed_ = false; modifier_alt_pressed_ = false; modifier_super_pressed_ = false; last_click_time_ = 0; + on_lost_focus(e); } -el::ModifierKeys ElementalControl::GetModifierKeys() { +el::ModifierKeys Window::GetModifierKeys() { auto modifiers = el::ModifierKeys::kNone; if (modifier_shift_pressed_) { modifiers |= el::ModifierKeys::kShift; @@ -241,7 +290,7 @@ el::ModifierKeys ElementalControl::GetModifierKeys() { return modifiers; } -void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) { +void Window::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) { if (!root_element()) { return; } @@ -364,8 +413,8 @@ void ElementalControl::OnKeyPress(KeyEvent& e, bool is_down, bool is_char) { } } -bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, - bool is_down) { +bool Window::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, + bool is_down) { bool shortcut_key = modifier_cntrl_pressed_; if (!el::Element::focused_element || !is_down || !shortcut_key) { return false; @@ -417,24 +466,32 @@ bool ElementalControl::CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, return true; } -void ElementalControl::OnKeyDown(KeyEvent& e) { - super::OnKeyDown(e); +void Window::OnKeyDown(KeyEvent& e) { + on_key_down(e); + if (e.is_handled()) { + return; + } OnKeyPress(e, true, false); } -void ElementalControl::OnKeyUp(KeyEvent& e) { - super::OnKeyUp(e); +void Window::OnKeyUp(KeyEvent& e) { + on_key_up(e); + if (e.is_handled()) { + return; + } OnKeyPress(e, false, false); } -void ElementalControl::OnKeyChar(KeyEvent& e) { - super::OnKeyChar(e); +void Window::OnKeyChar(KeyEvent& e) { OnKeyPress(e, true, true); OnKeyPress(e, false, true); } -void ElementalControl::OnMouseDown(MouseEvent& e) { - super::OnMouseDown(e); +void Window::OnMouseDown(MouseEvent& e) { + on_mouse_down(e); + if (e.is_handled()) { + return; + } if (!root_element()) { return; } @@ -463,8 +520,11 @@ void ElementalControl::OnMouseDown(MouseEvent& e) { } } -void ElementalControl::OnMouseMove(MouseEvent& e) { - super::OnMouseMove(e); +void Window::OnMouseMove(MouseEvent& e) { + on_mouse_move(e); + if (e.is_handled()) { + return; + } if (!root_element()) { return; } @@ -472,8 +532,11 @@ void ElementalControl::OnMouseMove(MouseEvent& e) { e.set_handled(true); } -void ElementalControl::OnMouseUp(MouseEvent& e) { - super::OnMouseUp(e); +void Window::OnMouseUp(MouseEvent& e) { + on_mouse_up(e); + if (e.is_handled()) { + return; + } if (!root_element()) { return; } @@ -494,8 +557,11 @@ void ElementalControl::OnMouseUp(MouseEvent& e) { } } -void ElementalControl::OnMouseWheel(MouseEvent& e) { - super::OnMouseWheel(e); +void Window::OnMouseWheel(MouseEvent& e) { + on_mouse_wheel(e); + if (e.is_handled()) { + return; + } if (!root_element()) { return; } @@ -505,32 +571,3 @@ void ElementalControl::OnMouseWheel(MouseEvent& e) { } // namespace ui } // namespace xe - -// This doesn't really belong here (it belongs in tb_system_[linux/windows].cpp. -// This is here since the proper implementations has not yet been done. -void el::util::RescheduleTimer(uint64_t fire_time) { - if (fire_time == el::MessageHandler::kNotSoon) { - return; - } - - uint64_t now = el::util::GetTimeMS(); - uint64_t delay_millis = fire_time >= now ? fire_time - now : 0; - xe::ui::elemental_loop_->PostDelayed([]() { - uint64_t next_fire_time = el::MessageHandler::GetNextMessageFireTime(); - uint64_t now = el::util::GetTimeMS(); - if (now < next_fire_time) { - // We timed out *before* we were supposed to (the OS is not playing nice). - // Calling ProcessMessages now won't achieve a thing so force a reschedule - // of the platform timer again with the same time. - // ReschedulePlatformTimer(next_fire_time, true); - return; - } - - el::MessageHandler::ProcessMessages(); - - // If we still have things to do (because we didn't process all messages, - // or because there are new messages), we need to rescedule, so call - // RescheduleTimer. - el::util::RescheduleTimer(el::MessageHandler::GetNextMessageFireTime()); - }, delay_millis); -} diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index 691335250..204b716cd 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -10,10 +10,13 @@ #ifndef XENIA_UI_WINDOW_H_ #define XENIA_UI_WINDOW_H_ +#include #include +#include "el/element.h" +#include "el/graphics/renderer.h" #include "xenia/base/delegate.h" -#include "xenia/ui/control.h" +#include "xenia/ui/graphics_context.h" #include "xenia/ui/loop.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/ui_event.h" @@ -21,14 +24,22 @@ namespace xe { namespace ui { -template -class Window : public T { - public: - ~Window() override = default; +typedef void* NativeWindowHandle; - virtual bool Initialize() { return true; } +class Window { + public: + static std::unique_ptr Create(Loop* loop, const std::wstring& title); + + virtual ~Window(); Loop* loop() const { return loop_; } + virtual NativeWindowHandle native_handle() const = 0; + + MenuItem* main_menu() const { return main_menu_.get(); } + void set_main_menu(std::unique_ptr main_menu) { + main_menu_ = std::move(main_menu); + OnMainMenuChange(); + } const std::wstring& title() const { return title_; } virtual bool set_title(const std::wstring& title) { @@ -39,65 +50,124 @@ class Window : public T { return true; } - MenuItem* menu() const { return menu_; } - void set_menu(MenuItem* menu) { - if (menu == menu_) { - return; - } - menu_ = menu; - OnSetMenu(menu); - } - - void Close() { - auto e = UIEvent(this); - on_closing(e); - - OnClose(); - - e = UIEvent(this); - on_closed(e); - } - virtual bool is_fullscreen() const { return false; } virtual void ToggleFullscreen(bool fullscreen) {} + bool has_focus() const { return has_focus_; } + virtual void set_focus(bool value) { has_focus_ = value; } + + bool is_cursor_visible() const { return is_cursor_visible_; } + virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; } + + int32_t width() const { return width_; } + int32_t height() const { return height_; } + virtual void Resize(int32_t width, int32_t height) = 0; + virtual void Resize(int32_t left, int32_t top, int32_t right, + int32_t bottom) = 0; + + GraphicsContext* context() const { return context_.get(); } + el::graphics::Renderer* renderer() const { return renderer_.get(); } + el::Element* root_element() const { return root_element_.get(); } + + virtual bool Initialize() { return true; } + void set_context(std::unique_ptr context) { + context_ = std::move(context); + } + virtual bool MakeReady(); + + bool LoadLanguage(std::string filename); + bool LoadSkin(std::string filename); + + void Layout(); + virtual void Invalidate(); + + virtual void Close() = 0; + public: - Delegate on_shown; - Delegate on_hidden; Delegate on_closing; Delegate on_closed; + Delegate on_command; + + Delegate on_resize; + Delegate on_layout; + Delegate on_painting; + Delegate on_paint; + Delegate on_painted; + + Delegate on_visible; + Delegate on_hidden; + + Delegate on_got_focus; + Delegate on_lost_focus; + + Delegate on_key_down; + Delegate on_key_up; + Delegate on_key_char; + + Delegate on_mouse_down; + Delegate on_mouse_move; + Delegate on_mouse_up; + Delegate on_mouse_wheel; + protected: - Window(Loop* loop, const std::wstring& title) - : T(0), loop_(loop), title_(title) {} + Window(Loop* loop, const std::wstring& title); - void OnShow() { - if (is_visible_) { - return; - } - is_visible_ = true; - auto e = UIEvent(this); - on_shown(e); - } + bool InitializeElemental(Loop* loop, el::graphics::Renderer* renderer); - void OnHide() { - if (!is_visible_) { - return; - } - is_visible_ = false; - auto e = UIEvent(this); - on_hidden(e); - } + virtual bool OnCreate(); + virtual void OnMainMenuChange(); + virtual void OnClose(); + virtual void OnDestroy(); - virtual void OnClose() {} + virtual void OnResize(UIEvent& e); + virtual void OnLayout(UIEvent& e); + virtual void OnPaint(UIEvent& e); - virtual void OnSetMenu(MenuItem* menu) {} + virtual void OnVisible(UIEvent& e); + virtual void OnHidden(UIEvent& e); - virtual void OnCommand(int id) {} + virtual void OnGotFocus(UIEvent& e); + virtual void OnLostFocus(UIEvent& e); + + virtual void OnKeyDown(KeyEvent& e); + virtual void OnKeyUp(KeyEvent& e); + virtual void OnKeyChar(KeyEvent& e); + + virtual void OnMouseDown(MouseEvent& e); + virtual void OnMouseMove(MouseEvent& e); + virtual void OnMouseUp(MouseEvent& e); + virtual void OnMouseWheel(MouseEvent& e); + + el::ModifierKeys GetModifierKeys(); + void OnKeyPress(KeyEvent& e, bool is_down, bool is_char); + bool CheckShortcutKey(KeyEvent& e, el::SpecialKey special_key, bool is_down); Loop* loop_ = nullptr; + std::unique_ptr main_menu_; std::wstring title_; - MenuItem* menu_ = nullptr; + int32_t width_ = 0; + int32_t height_ = 0; + bool has_focus_ = true; + bool is_cursor_visible_ = true; + + std::unique_ptr context_; + std::unique_ptr renderer_; + std::unique_ptr root_element_; + + uint32_t frame_count_ = 0; + uint32_t fps_ = 0; + uint64_t fps_update_time_ = 0; + uint64_t fps_frame_count_ = 0; + + bool modifier_shift_pressed_ = false; + bool modifier_cntrl_pressed_ = false; + bool modifier_alt_pressed_ = false; + bool modifier_super_pressed_ = false; + uint64_t last_click_time_ = 0; + int last_click_x_ = 0; + int last_click_y_ = 0; + int last_click_counter_ = 0; }; } // namespace ui diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc new file mode 100644 index 000000000..e99ef2303 --- /dev/null +++ b/src/xenia/ui/window_win.cc @@ -0,0 +1,569 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2014 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include + +#include "xenia/base/assert.h" +#include "xenia/base/logging.h" +#include "xenia/ui/window_win.h" + +namespace xe { +namespace ui { + +std::unique_ptr Window::Create(Loop* loop, const std::wstring& title) { + return std::make_unique(loop, title); +} + +Win32Window::Win32Window(Loop* loop, const std::wstring& title) + : Window(loop, title) {} + +Win32Window::~Win32Window() { + OnDestroy(); + if (hwnd_) { + SetWindowLongPtr(hwnd_, GWLP_USERDATA, 0); + CloseWindow(hwnd_); + hwnd_ = nullptr; + } +} + +bool Win32Window::Initialize() { return OnCreate(); } + +bool Win32Window::OnCreate() { + HINSTANCE hInstance = GetModuleHandle(nullptr); + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wcex.lpfnWndProc = Win32Window::WndProcThunk; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1); + wcex.hIconSm = nullptr; // LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = L"XeniaWindowClass"; + if (!RegisterClassEx(&wcex)) { + XELOGE("RegisterClassEx failed"); + return false; + } + + // Setup initial size. + DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + DWORD window_ex_style = WS_EX_APPWINDOW | WS_EX_CONTROLPARENT; + RECT rc = {0, 0, width_, height_}; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); + + // Create window. + hwnd_ = CreateWindowEx(window_ex_style, L"XeniaWindowClass", title_.c_str(), + window_style, rc.left, rc.top, rc.right - rc.left, + rc.bottom - rc.top, nullptr, nullptr, hInstance, this); + if (!hwnd_) { + XELOGE("CreateWindow failed"); + return false; + } + + // Disable flicks. + ATOM atom = GlobalAddAtom(L"MicrosoftTabletPenServiceProperty"); + const DWORD_PTR dwHwndTabletProperty = + TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) + // gesture + TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves) + TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button + // down (circle) + TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, drag down, + // drag up) + TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING | + TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA; + SetProp(hwnd_, L"MicrosoftTabletPenServiceProperty", + reinterpret_cast(dwHwndTabletProperty)); + GlobalDeleteAtom(atom); + + // Enable DWM elevation. + EnableMMCSS(); + + ShowWindow(hwnd_, SW_SHOWNORMAL); + UpdateWindow(hwnd_); + + // Initial state. + if (!is_cursor_visible_) { + ShowCursor(FALSE); + } + if (has_focus_) { + SetFocus(hwnd_); + } + + return super::OnCreate(); +} + +void Win32Window::EnableMMCSS() { + HMODULE hLibrary = LoadLibrary(L"DWMAPI.DLL"); + if (!hLibrary) { + return; + } + + typedef HRESULT(__stdcall * PDwmEnableMMCSS)(BOOL); + PDwmEnableMMCSS pDwmEnableMMCSS = + (PDwmEnableMMCSS)GetProcAddress(hLibrary, "DwmEnableMMCSS"); + if (pDwmEnableMMCSS) { + pDwmEnableMMCSS(TRUE); + } + + typedef HRESULT(__stdcall * PDwmSetPresentParameters)( + HWND, DWM_PRESENT_PARAMETERS*); + PDwmSetPresentParameters pDwmSetPresentParameters = + (PDwmSetPresentParameters)GetProcAddress(hLibrary, + "DwmSetPresentParameters"); + if (pDwmSetPresentParameters) { + DWM_PRESENT_PARAMETERS pp; + std::memset(&pp, 0, sizeof(DWM_PRESENT_PARAMETERS)); + pp.cbSize = sizeof(DWM_PRESENT_PARAMETERS); + pp.fQueue = FALSE; + pp.cBuffer = 2; + pp.fUseSourceRate = FALSE; + pp.cRefreshesPerFrame = 1; + pp.eSampling = DWM_SOURCE_FRAME_SAMPLING_POINT; + pDwmSetPresentParameters(hwnd_, &pp); + } + + FreeLibrary(hLibrary); +} + +void Win32Window::OnDestroy() { + super::OnDestroy(); + hwnd_ = nullptr; +} + +void Win32Window::OnClose() { + if (!closing_ && hwnd_) { + closing_ = true; + CloseWindow(hwnd_); + } + super::OnClose(); +} + +bool Win32Window::set_title(const std::wstring& title) { + if (!super::set_title(title)) { + return false; + } + SetWindowText(hwnd_, title.c_str()); + return true; +} + +bool Win32Window::is_fullscreen() const { + DWORD style = GetWindowLong(hwnd_, GWL_STYLE); + return (style & WS_OVERLAPPEDWINDOW) != WS_OVERLAPPEDWINDOW; +} + +void Win32Window::ToggleFullscreen(bool fullscreen) { + if (fullscreen == is_fullscreen()) { + return; + } + + DWORD style = GetWindowLong(hwnd_, GWL_STYLE); + if (fullscreen) { + // Kill our borders and resize to take up entire primary monitor. + // http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx + MONITORINFO mi = {sizeof(mi)}; + if (GetWindowPlacement(hwnd_, &windowed_pos_) && + GetMonitorInfo(MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY), + &mi)) { + SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + ::SetMenu(hwnd_, NULL); + + // Call into parent class to get around menu resizing code. + Resize(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, + mi.rcMonitor.bottom); + } + } else { + // Reinstate borders, resize to 1280x720 + SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(hwnd_, &windowed_pos_); + + auto main_menu = reinterpret_cast(main_menu_.get()); + if (main_menu) { + ::SetMenu(hwnd_, main_menu->handle()); + } + } +} + +void Win32Window::set_cursor_visible(bool value) { + if (is_cursor_visible_ == value) { + return; + } + if (value) { + ShowCursor(TRUE); + SetCursor(nullptr); + } else { + ShowCursor(FALSE); + } +} + +void Win32Window::set_focus(bool value) { + if (has_focus_ == value) { + return; + } + if (hwnd_) { + if (value) { + SetFocus(hwnd_); + } else { + SetFocus(nullptr); + } + } else { + has_focus_ = value; + } +} + +void Win32Window::Resize(int32_t width, int32_t height) { + RECT rc = {0, 0, width, height}; + bool has_menu = main_menu_ ? true : false; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu); + if (true) { + rc.right += 100 - rc.left; + rc.left = 100; + rc.bottom += 100 - rc.top; + rc.top = 100; + } + MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + TRUE); +} + +void Win32Window::Resize(int32_t left, int32_t top, int32_t right, + int32_t bottom) { + RECT rc = {left, top, right, bottom}; + bool has_menu = main_menu_ ? true : false; + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, has_menu); + MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + TRUE); +} + +void Win32Window::OnResize(UIEvent& e) { + RECT client_rect; + GetClientRect(hwnd_, &client_rect); + int32_t width = client_rect.right - client_rect.left; + int32_t height = client_rect.bottom - client_rect.top; + if (width != width_ || height != height_) { + width_ = width; + height_ = height; + Layout(); + } + super::OnResize(e); +} + +void Win32Window::Invalidate() { + super::Invalidate(); + InvalidateRect(hwnd_, nullptr, FALSE); +} + +void Win32Window::Close() { + if (closing_) { + return; + } + closing_ = true; + Close(); + OnClose(); + DestroyWindow(hwnd_); +} + +void Win32Window::OnMainMenuChange() { + auto main_menu = reinterpret_cast(main_menu_.get()); + // Don't actually set the menu if we're fullscreen. We'll do that later. + if (main_menu && !is_fullscreen()) { + ::SetMenu(hwnd_, main_menu->handle()); + } +} + +LRESULT CALLBACK Win32Window::WndProcThunk(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam) { + Win32Window* window = nullptr; + if (message == WM_NCCREATE) { + auto create_struct = reinterpret_cast(lParam); + window = reinterpret_cast(create_struct->lpCreateParams); + SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(LONG_PTR)window); + } else { + window = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + } + if (window) { + return window->WndProc(hWnd, message, wParam, lParam); + } else { + return DefWindowProc(hWnd, message, wParam, lParam); + } +} + +LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) { + if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) { + if (HandleMouse(message, wParam, lParam)) { + SetFocus(hwnd_); + return 0; + } else { + return DefWindowProc(hWnd, message, wParam, lParam); + } + } else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) { + if (HandleKeyboard(message, wParam, lParam)) { + SetFocus(hwnd_); + return 0; + } else { + return DefWindowProc(hWnd, message, wParam, lParam); + } + } + + switch (message) { + case WM_NCCREATE: + break; + case WM_CREATE: + break; + case WM_DESTROY: + OnDestroy(); + break; + case WM_CLOSE: + closing_ = true; + Close(); + OnClose(); + break; + + case WM_MOVING: + break; + case WM_MOVE: + break; + case WM_SIZING: + break; + case WM_SIZE: { + auto e = UIEvent(this); + OnResize(e); + break; + } + + case WM_PAINT: { + ValidateRect(hwnd_, nullptr); + auto e = UIEvent(this); + OnPaint(e); + return 0; // Ignored because of custom paint. + break; + } + case WM_ERASEBKGND: + return 0; // Ignored because of custom paint. + case WM_DISPLAYCHANGE: + break; + + case WM_ACTIVATEAPP: + case WM_SHOWWINDOW: { + if (wParam == TRUE) { + auto e = UIEvent(this); + OnVisible(e); + } else { + auto e = UIEvent(this); + OnHidden(e); + } + break; + } + + case WM_KILLFOCUS: { + has_focus_ = false; + auto e = UIEvent(this); + OnLostFocus(e); + break; + } + case WM_SETFOCUS: { + has_focus_ = true; + auto e = UIEvent(this); + OnGotFocus(e); + break; + } + + case WM_TABLET_QUERYSYSTEMGESTURESTATUS: + return + // disables press and hold (right-click) gesture + TABLET_DISABLE_PRESSANDHOLD | + // disables UI feedback on pen up (waves) + TABLET_DISABLE_PENTAPFEEDBACK | + // disables UI feedback on pen button down (circle) + TABLET_DISABLE_PENBARRELFEEDBACK | + // disables pen flicks (back, forward, drag down, drag up) + TABLET_DISABLE_FLICKS | TABLET_DISABLE_TOUCHSWITCH | + TABLET_DISABLE_SMOOTHSCROLLING | TABLET_DISABLE_TOUCHUIFORCEON | + TABLET_ENABLE_MULTITOUCHDATA; + + case WM_MENUCOMMAND: { + // TODO: Redirect this to MenuItem's on_selected delegate. + MENUINFO menu_info = {0}; + menu_info.cbSize = sizeof(menu_info); + menu_info.fMask = MIM_MENUDATA; + GetMenuInfo(HMENU(lParam), &menu_info); + auto parent_item = reinterpret_cast(menu_info.dwMenuData); + auto child_item = + reinterpret_cast(parent_item->child(wParam)); + assert_not_null(child_item); + UIEvent e(this); + child_item->OnSelected(e); + } break; + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +bool Win32Window::HandleMouse(UINT message, WPARAM wParam, LPARAM lParam) { + int32_t x = GET_X_LPARAM(lParam); + int32_t y = GET_Y_LPARAM(lParam); + if (message == WM_MOUSEWHEEL) { + POINT pt = {x, y}; + ScreenToClient(hwnd_, &pt); + x = pt.x; + y = pt.y; + } + + MouseEvent::Button button = MouseEvent::Button::kNone; + int32_t dx = 0; + int32_t dy = 0; + switch (message) { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + button = MouseEvent::Button::kLeft; + break; + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + button = MouseEvent::Button::kRight; + break; + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + button = MouseEvent::Button::kMiddle; + break; + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + switch (GET_XBUTTON_WPARAM(wParam)) { + case XBUTTON1: + button = MouseEvent::Button::kX1; + break; + case XBUTTON2: + button = MouseEvent::Button::kX2; + break; + default: + return false; + } + break; + case WM_MOUSEMOVE: + button = MouseEvent::Button::kNone; + break; + case WM_MOUSEWHEEL: + button = MouseEvent::Button::kNone; + dx = 0; // ? + dy = GET_WHEEL_DELTA_WPARAM(wParam); + break; + default: + // Double click/etc? + return true; + } + + auto e = MouseEvent(this, button, x, y, dx, dy); + switch (message) { + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_XBUTTONDOWN: + OnMouseDown(e); + break; + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + OnMouseUp(e); + break; + case WM_MOUSEMOVE: + OnMouseMove(e); + break; + case WM_MOUSEWHEEL: + OnMouseWheel(e); + break; + } + return e.is_handled(); +} + +bool Win32Window::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam) { + auto e = KeyEvent(this, (int)wParam); + switch (message) { + case WM_KEYDOWN: + OnKeyDown(e); + break; + case WM_KEYUP: + OnKeyUp(e); + break; + case WM_CHAR: + OnKeyChar(e); + break; + } + return e.is_handled(); +} + +std::unique_ptr MenuItem::Create(Type type, + const std::wstring& text, + const std::wstring& hotkey, + std::function callback) { + return std::make_unique(type, text, hotkey, callback); +} + +Win32MenuItem::Win32MenuItem(Type type, const std::wstring& text, + const std::wstring& hotkey, + std::function callback) + : MenuItem(type, text, hotkey, std::move(callback)) { + switch (type) { + case MenuItem::Type::kNormal: + handle_ = CreateMenu(); + break; + case MenuItem::Type::kPopup: + handle_ = CreatePopupMenu(); + break; + } + if (handle_) { + MENUINFO menu_info = {0}; + menu_info.cbSize = sizeof(menu_info); + menu_info.fMask = MIM_MENUDATA | MIM_STYLE; + menu_info.dwMenuData = ULONG_PTR(this); + menu_info.dwStyle = MNS_NOTIFYBYPOS; + SetMenuInfo(handle_, &menu_info); + } +} + +Win32MenuItem::~Win32MenuItem() { + if (handle_) { + DestroyMenu(handle_); + } +} + +void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) { + auto child_item = static_cast(generic_child_item); + + switch (child_item->type()) { + case MenuItem::Type::kPopup: + AppendMenuW(handle_, MF_POPUP, + reinterpret_cast(child_item->handle()), + child_item->text().c_str()); + break; + case MenuItem::Type::kSeparator: + AppendMenuW(handle_, MF_SEPARATOR, UINT_PTR(child_item->handle_), 0); + break; + case MenuItem::Type::kString: + auto full_name = child_item->text(); + if (!child_item->hotkey().empty()) { + full_name += L"\t" + child_item->hotkey(); + } + AppendMenuW(handle_, MF_STRING, UINT_PTR(child_item->handle_), + full_name.c_str()); + break; + } +} + +void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) { + auto child_item = static_cast(generic_child_item); + // +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/win32/win32_control.h b/src/xenia/ui/window_win.h similarity index 52% rename from src/xenia/ui/win32/win32_control.h rename to src/xenia/ui/window_win.h index b99fa7704..f25323ddd 100644 --- a/src/xenia/ui/win32/win32_control.h +++ b/src/xenia/ui/window_win.h @@ -7,45 +7,49 @@ ****************************************************************************** */ -#ifndef XENIA_UI_WIN32_WIN32_CONTROL_H_ -#define XENIA_UI_WIN32_WIN32_CONTROL_H_ +#ifndef XENIA_UI_WINDOW_WIN_H_ +#define XENIA_UI_WINDOW_WIN_H_ -#include "xenia/base/platform.h" -#include "xenia/ui/control.h" +#include +#include + +#include "xenia/ui/menu_item.h" +#include "xenia/ui/window.h" namespace xe { namespace ui { -namespace win32 { -class Win32Control : public Control { +class Win32Window : public Window { + using super = Window; + public: - ~Win32Control() override; + Win32Window(Loop* loop, const std::wstring& title); + ~Win32Window() override; + NativeWindowHandle native_handle() const override { return hwnd_; } HWND hwnd() const { return hwnd_; } - HWND parent_hwnd() const { - return parent_ ? static_cast(parent_)->hwnd() : nullptr; - } + + bool set_title(const std::wstring& title) override; + + bool is_fullscreen() const override; + void ToggleFullscreen(bool fullscreen) override; + + void set_cursor_visible(bool value) override; + void set_focus(bool value) override; void Resize(int32_t width, int32_t height) override; void Resize(int32_t left, int32_t top, int32_t right, int32_t bottom) override; - void ResizeToFill(int32_t pad_left, int32_t pad_top, int32_t pad_right, - int32_t pad_bottom) override; - void Invalidate() override; - void set_cursor_visible(bool value) override; - void set_enabled(bool value) override; - void set_visible(bool value) override; - void set_focus(bool value) override; + bool Initialize() override; + void Invalidate() override; + void Close() override; protected: - explicit Win32Control(uint32_t flags); - - void OnCreate() override; + bool OnCreate() override; + void OnMainMenuChange() override; void OnDestroy() override; - - void OnChildAdded(Control* child_control) override; - void OnChildRemoved(Control* child_control) override; + void OnClose() override; void OnResize(UIEvent& e) override; @@ -54,16 +58,37 @@ class Win32Control : public Control { virtual LRESULT WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); - HWND hwnd_ = nullptr; - bool invalidated_ = true; - private: + void EnableMMCSS(); bool HandleMouse(UINT message, WPARAM wParam, LPARAM lParam); bool HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam); + + HWND hwnd_ = nullptr; + bool invalidated_ = true; + bool closing_ = false; + + WINDOWPLACEMENT windowed_pos_; +}; + +class Win32MenuItem : public MenuItem { + public: + Win32MenuItem(Type type, const std::wstring& text, const std::wstring& hotkey, + std::function callback); + ~Win32MenuItem() override; + + HMENU handle() { return handle_; } + + using MenuItem::OnSelected; + + protected: + void OnChildAdded(MenuItem* child_item) override; + void OnChildRemoved(MenuItem* child_item) override; + + private: + HMENU handle_ = nullptr; }; -} // namespace win32 } // namespace ui } // namespace xe -#endif // XENIA_UI_WIN32_WIN32_CONTROL_H_ +#endif // XENIA_UI_WINDOW_WIN_H_ diff --git a/src/xenia/xenia_main.cc b/src/xenia/xenia_main.cc index d32240b7c..3994396c1 100644 --- a/src/xenia/xenia_main.cc +++ b/src/xenia/xenia_main.cc @@ -12,6 +12,7 @@ #include "xenia/base/logging.h" #include "xenia/base/main.h" #include "xenia/emulator.h" +#include "xenia/emulator_window.h" #include "xenia/kernel/kernel.h" #include "xenia/profiling.h" #include "xenia/ui/file_picker.h" @@ -24,9 +25,15 @@ int xenia_main(std::vector& args) { Profiler::Initialize(); Profiler::ThreadEnter("main"); - // Create the emulator. + // Create the emulator but don't initialize so we can setup the window. auto emulator = std::make_unique(L""); - X_STATUS result = emulator->Setup(); + + // Main emulator display window. + auto emulator_window = EmulatorWindow::Create(emulator.get()); + + // Setup and initialize all subsystems. If we can't do something + // (unsupported system, memory issues, etc) this will fail early. + X_STATUS result = emulator->Setup(emulator_window->window()); if (XFAILED(result)) { XELOGE("Failed to setup emulator: %.8X", result); return 1; @@ -48,20 +55,20 @@ int xenia_main(std::vector& args) { // If no path passed, ask the user. if (path.empty()) { - ui::PlatformFilePicker file_picker; - file_picker.set_mode(ui::FilePicker::Mode::kOpen); - file_picker.set_type(ui::FilePicker::Type::kFile); - file_picker.set_multi_selection(false); - file_picker.set_title(L"Select Content Package"); - file_picker.set_extensions({ + auto file_picker = xe::ui::FilePicker::Create(); + file_picker->set_mode(ui::FilePicker::Mode::kOpen); + file_picker->set_type(ui::FilePicker::Type::kFile); + file_picker->set_multi_selection(false); + file_picker->set_title(L"Select Content Package"); + file_picker->set_extensions({ {L"Supported Files", L"*.iso;*.xex;*.xcp;*.*"}, {L"Disc Image (*.iso)", L"*.iso"}, {L"Xbox Executable (*.xex)", L"*.xex"}, //{ L"Content Package (*.xcp)", L"*.xcp" }, {L"All Files (*.*)", L"*.*"}, }); - if (file_picker.Show(emulator->display_window()->hwnd())) { - auto selected_files = file_picker.selected_files(); + if (file_picker->Show(emulator->display_window()->native_handle())) { + auto selected_files = file_picker->selected_files(); if (!selected_files.empty()) { path = selected_files[0]; } @@ -83,6 +90,8 @@ int xenia_main(std::vector& args) { } emulator.reset(); + emulator_window.reset(); + Profiler::Dump(); Profiler::Shutdown(); return 0; diff --git a/third_party/elemental-forms b/third_party/elemental-forms index a95e9a198..879a034dd 160000 --- a/third_party/elemental-forms +++ b/third_party/elemental-forms @@ -1 +1 @@ -Subproject commit a95e9a1984029382ae8e35260725d5697a702f8b +Subproject commit 879a034ddf323744654bb810da5fe8e15143f6ea diff --git a/xenia.vcxproj b/xenia.vcxproj index aee6c5409..c327f1418 100644 --- a/xenia.vcxproj +++ b/xenia.vcxproj @@ -106,10 +106,19 @@ + + + + + + $(SolutionDir)third_party/elemental-forms;$(SolutionDir);. + $(SolutionDir)third_party/elemental-forms;$(SolutionDir);. + $(SolutionDir)third_party/elemental-forms;$(SolutionDir);. + diff --git a/xenia.vcxproj.filters b/xenia.vcxproj.filters index 8f0eec36d..528be52b4 100644 --- a/xenia.vcxproj.filters +++ b/xenia.vcxproj.filters @@ -14,6 +14,9 @@ {a3d918ab-c42b-469d-9950-ec4656b77b32} + + {81255c06-3cf7-48ad-900a-f7c614d134ac} + @@ -22,10 +25,21 @@ src\xenia + + src\xenia + src\xenia\base + + src\xenia + + + + + src\xenia\ui + \ No newline at end of file