From 6d591970ab4263210e70884aedd2608222a6b0b5 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 18 Jan 2019 09:01:11 -0500 Subject: [PATCH 01/36] [vulkan linux] Fix native_platform_handle with gtk Use gtk connection as handle and use it when creating surface. --- src/xenia/ui/vulkan/vulkan_context.cc | 4 ---- src/xenia/ui/window_gtk.cc | 6 ++++++ src/xenia/ui/window_gtk.h | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index 50f51ad74..bc6600389 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -73,10 +73,6 @@ bool VulkanContext::Initialize() { #ifdef GDK_WINDOWING_X11 GtkWidget* window_handle = static_cast(target_window_->native_handle()); - GdkDisplay* gdk_display = gtk_widget_get_display(window_handle); - assert(GDK_IS_X11_DISPLAY(gdk_display)); - xcb_connection_t* connection = - XGetXCBConnection(gdk_x11_display_get_xdisplay(gdk_display)); xcb_window_t window = gdk_x11_window_get_xid(gtk_widget_get_window(window_handle)); VkXcbSurfaceCreateInfoKHR create_info; diff --git a/src/xenia/ui/window_gtk.cc b/src/xenia/ui/window_gtk.cc index 56248bd2d..7d11d316c 100644 --- a/src/xenia/ui/window_gtk.cc +++ b/src/xenia/ui/window_gtk.cc @@ -9,6 +9,8 @@ #include +#include + #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/platform_linux.h" @@ -81,6 +83,10 @@ void GTKWindow::Create() { NULL); g_signal_connect(G_OBJECT(window_), "event", G_CALLBACK(gtk_event_handler_), reinterpret_cast(this)); + + GdkDisplay* gdk_display = gtk_widget_get_display(window_); + assert(GDK_IS_X11_DISPLAY(gdk_display)); + connection_ = XGetXCBConnection(gdk_x11_display_get_xdisplay(gdk_display)); } bool GTKWindow::OnCreate() { diff --git a/src/xenia/ui/window_gtk.h b/src/xenia/ui/window_gtk.h index c20c0d5d6..ce28cfcd0 100644 --- a/src/xenia/ui/window_gtk.h +++ b/src/xenia/ui/window_gtk.h @@ -15,6 +15,7 @@ #include #include +#include #include "xenia/base/platform_linux.h" #include "xenia/ui/menu_item.h" @@ -31,7 +32,7 @@ class GTKWindow : public Window { ~GTKWindow() override; NativePlatformHandle native_platform_handle() const override { - return nullptr; + return connection_; } NativeWindowHandle native_handle() const override { return window_; } @@ -74,6 +75,7 @@ class GTKWindow : public Window { private: void Create(); GtkWidget* window_; + xcb_connection_t* connection_; friend void gtk_event_handler_(GtkWidget*, GdkEvent*, gpointer); bool HandleMouse(GdkEventAny* event); From 5ed4f8809168e145ab3f81cbbecdb3bc2e965597 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 16 Jan 2019 07:46:35 -0500 Subject: [PATCH 02/36] [vulkan linux] Add extension requirement for XCB --- src/xenia/ui/vulkan/vulkan_instance.cc | 15 +++++++++++++++ src/xenia/ui/vulkan/vulkan_provider.cc | 9 ++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_instance.cc b/src/xenia/ui/vulkan/vulkan_instance.cc index b324f86f2..ea6f59ca0 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.cc +++ b/src/xenia/ui/vulkan/vulkan_instance.cc @@ -64,6 +64,21 @@ VulkanInstance::VulkanInstance() { DeclareRequiredExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME, Version::Make(0, 0, 0), true); + DeclareRequiredExtension(VK_KHR_SURFACE_EXTENSION_NAME, + Version::Make(0, 0, 0), true); +#if XE_PLATFORM_WIN32 + DeclareRequiredExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, + Version::Make(0, 0, 0), true); +#elif XE_PLATFORM_LINUX +#ifdef GDK_WINDOWING_X11 + DeclareRequiredExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, + Version::Make(0, 0, 0), true); +#else +#error No Vulkan surface extension for the GDK backend defined yet. +#endif +#else +#error No Vulkan surface extension for the platform defined yet. +#endif } VulkanInstance::~VulkanInstance() { DestroyInstance(); } diff --git a/src/xenia/ui/vulkan/vulkan_provider.cc b/src/xenia/ui/vulkan/vulkan_provider.cc index 119f03992..ab566565f 100644 --- a/src/xenia/ui/vulkan/vulkan_provider.cc +++ b/src/xenia/ui/vulkan/vulkan_provider.cc @@ -52,10 +52,13 @@ bool VulkanProvider::Initialize() { instance_ = std::make_unique(); // Always enable the swapchain. + instance_->DeclareRequiredExtension(VK_KHR_SURFACE_EXTENSION_NAME, + Version::Make(0, 0, 0), false); #if XE_PLATFORM_WIN32 - instance_->DeclareRequiredExtension("VK_KHR_surface", Version::Make(0, 0, 0), - false); - instance_->DeclareRequiredExtension("VK_KHR_win32_surface", + instance_->DeclareRequiredExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, + Version::Make(0, 0, 0), false); +#elif XE_PLATFORM_LINUX + instance_->DeclareRequiredExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, Version::Make(0, 0, 0), false); #endif From c4a2dff09936daddbfbc3d4526ab2770de43613f Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Thu, 17 Jan 2019 18:49:23 -0800 Subject: [PATCH 03/36] [vulkan] Add Report Callback to instance create --- src/xenia/ui/vulkan/vulkan_instance.cc | 87 +++++++++++++++----------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_instance.cc b/src/xenia/ui/vulkan/vulkan_instance.cc index ea6f59ca0..6f258d6ea 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.cc +++ b/src/xenia/ui/vulkan/vulkan_instance.cc @@ -218,6 +218,43 @@ bool VulkanInstance::QueryGlobals() { return true; } +VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + uint64_t object, size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage, void* pUserData) { + if (strcmp(pLayerPrefix, "Validation") == 0) { + const char* blacklist[] = { + "bound but it was never updated. You may want to either update it or " + "not bind it.", + "is being used in draw but has not been updated.", + }; + for (uint32_t i = 0; i < xe::countof(blacklist); ++i) { + if (strstr(pMessage, blacklist[i]) != nullptr) { + return false; + } + } + } + + auto instance = reinterpret_cast(pUserData); + const char* message_type = "UNKNOWN"; + if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { + message_type = "ERROR"; + } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { + message_type = "WARN"; + } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { + message_type = "PERF WARN"; + } else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { + message_type = "INFO"; + } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { + message_type = "DEBUG"; + } + + XELOGVK("[{}/{}:{}] {}", pLayerPrefix, message_type, messageCode, pMessage); + return false; +} + bool VulkanInstance::CreateInstance() { XELOGVK("Verifying layers and extensions..."); @@ -239,10 +276,21 @@ bool VulkanInstance::CreateInstance() { XELOGVK("Initializing application instance..."); + VkDebugReportCallbackCreateInfoEXT debug_info; + debug_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + debug_info.pNext = nullptr; + // TODO(benvanik): flags to set these. + debug_info.flags = + VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | + VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | + VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT; + debug_info.pfnCallback = &DebugMessageCallback; + debug_info.pUserData = this; + // TODO(benvanik): use GetEntryInfo? VkApplicationInfo application_info; application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - application_info.pNext = nullptr; + application_info.pNext = &debug_info; application_info.pApplicationName = "xenia"; application_info.applicationVersion = 1; application_info.pEngineName = "xenia"; @@ -306,43 +354,6 @@ void VulkanInstance::DestroyInstance() { handle = nullptr; } -VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags, - VkDebugReportObjectTypeEXT objectType, - uint64_t object, size_t location, - int32_t messageCode, - const char* pLayerPrefix, - const char* pMessage, void* pUserData) { - if (strcmp(pLayerPrefix, "Validation") == 0) { - const char* blacklist[] = { - "bound but it was never updated. You may want to either update it or " - "not bind it.", - "is being used in draw but has not been updated.", - }; - for (uint32_t i = 0; i < xe::countof(blacklist); ++i) { - if (strstr(pMessage, blacklist[i]) != nullptr) { - return false; - } - } - } - - auto instance = reinterpret_cast(pUserData); - const char* message_type = "UNKNOWN"; - if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { - message_type = "ERROR"; - } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { - message_type = "WARN"; - } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { - message_type = "PERF WARN"; - } else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { - message_type = "INFO"; - } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { - message_type = "DEBUG"; - } - - XELOGVK("[{}/{}:{}] {}", pLayerPrefix, message_type, messageCode, pMessage); - return false; -} - void VulkanInstance::EnableDebugValidation() { if (dbg_report_callback_) { DisableDebugValidation(); From 31a8d189f00e839e39b0633ff757e09536c0de4b Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Fri, 18 Jan 2019 09:05:42 -0500 Subject: [PATCH 04/36] [vulkan] Fix typos --- src/xenia/ui/vulkan/vulkan_context.cc | 1 - src/xenia/ui/vulkan/vulkan_instance.cc | 2 +- src/xenia/ui/window_demo.cc | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index bc6600389..e39eada39 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -52,7 +52,6 @@ VulkanContext::~VulkanContext() { bool VulkanContext::Initialize() { auto provider = static_cast(provider_); - auto device = provider->device(); if (target_window_) { // Create swap chain used to present to the window. diff --git a/src/xenia/ui/vulkan/vulkan_instance.cc b/src/xenia/ui/vulkan/vulkan_instance.cc index 6f258d6ea..47ddfcf90 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.cc +++ b/src/xenia/ui/vulkan/vulkan_instance.cc @@ -336,7 +336,7 @@ bool VulkanInstance::CreateInstance() { return false; } - // Load Vulkan entrypoints and extensions. + // Load Vulkan entry points and extensions. volkLoadInstance(handle); // Enable debug validation, if needed. diff --git a/src/xenia/ui/window_demo.cc b/src/xenia/ui/window_demo.cc index a6a05140a..cda8a6b83 100644 --- a/src/xenia/ui/window_demo.cc +++ b/src/xenia/ui/window_demo.cc @@ -70,7 +70,7 @@ int window_demo_main(const std::vector& args) { std::unique_ptr graphics_provider; loop->PostSynchronous([&window, &graphics_provider]() { // Create graphics provider and an initial context for the window. - // The window will finish initialization wtih the context (loading + // The window will finish initialization with the context (loading // resources, etc). graphics_provider = CreateDemoGraphicsProvider(window.get()); window->set_context(graphics_provider->CreateContext(window.get())); From cdf77d21bc2f9acaec112e5a4519ba4a2d300042 Mon Sep 17 00:00:00 2001 From: Doug Johnson Date: Mon, 8 Jan 2018 02:57:55 -0700 Subject: [PATCH 05/36] UI: Improve GTK GUI Abstractions for Linux --- src/xenia/ui/loop_gtk.cc | 30 +++---- src/xenia/ui/window.h | 5 +- src/xenia/ui/window_gtk.cc | 165 +++++++++++++++++++++++++------------ src/xenia/ui/window_gtk.h | 12 ++- 4 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/xenia/ui/loop_gtk.cc b/src/xenia/ui/loop_gtk.cc index 4a753c853..a2b704aeb 100644 --- a/src/xenia/ui/loop_gtk.cc +++ b/src/xenia/ui/loop_gtk.cc @@ -17,15 +17,6 @@ namespace xe { namespace ui { -class PostedFn { - public: - explicit PostedFn(std::function fn) : fn_(std::move(fn)) {} - void Call() { fn_(); } - - private: - std::function fn_; -}; - std::unique_ptr Loop::Create() { return std::make_unique(); } GTKLoop::GTKLoop() : thread_id_() { @@ -56,24 +47,33 @@ bool GTKLoop::is_on_loop_thread() { } gboolean _posted_fn_thunk(gpointer posted_fn) { - PostedFn* Fn = reinterpret_cast(posted_fn); - Fn->Call(); + // convert gpointer back to std::function, call it, then free std::function + std::function* f = + reinterpret_cast*>(posted_fn); + std::function& func = *f; + func(); + delete f; + // Tells GDK we don't want to run this again return G_SOURCE_REMOVE; } void GTKLoop::Post(std::function fn) { assert_true(thread_id_ != std::thread::id()); - gdk_threads_add_idle(_posted_fn_thunk, - reinterpret_cast(new PostedFn(std::move(fn)))); + // converting std::function to a generic pointer for gdk + gdk_threads_add_idle(_posted_fn_thunk, reinterpret_cast( + new std::function(fn))); } void GTKLoop::PostDelayed(std::function fn, uint64_t delay_millis) { gdk_threads_add_timeout( delay_millis, _posted_fn_thunk, - reinterpret_cast(new PostedFn(std::move(fn)))); + reinterpret_cast(new std::function(fn))); } -void GTKLoop::Quit() { assert_true(thread_id_ != std::thread::id()); } +void GTKLoop::Quit() { + assert_true(thread_id_ != std::thread::id()); + Post([]() { gtk_main_quit(); }); +} void GTKLoop::AwaitQuit() { quit_fence_.Wait(); } diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index accdc65b9..d8d3ac00b 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -172,8 +172,9 @@ class Window { Loop* loop_ = nullptr; std::unique_ptr main_menu_; std::string title_; - int32_t width_ = 0; - int32_t height_ = 0; + // GTK must have a default value here that isn't 0 + int32_t width_ = 1280; + int32_t height_ = 720; bool has_focus_ = true; bool is_cursor_visible_ = true; bool is_imgui_input_enabled_ = false; diff --git a/src/xenia/ui/window_gtk.cc b/src/xenia/ui/window_gtk.cc index 7d11d316c..a785af2df 100644 --- a/src/xenia/ui/window_gtk.cc +++ b/src/xenia/ui/window_gtk.cc @@ -7,11 +7,13 @@ ****************************************************************************** */ +#include #include #include #include "xenia/base/assert.h" +#include "xenia/base/clock.h" #include "xenia/base/logging.h" #include "xenia/base/platform_linux.h" #include "xenia/ui/window_gtk.h" @@ -19,15 +21,6 @@ namespace xe { namespace ui { -class FnWrapper { - public: - explicit FnWrapper(std::function fn) : fn_(std::move(fn)) {} - void Call() { fn_(); } - - private: - std::function fn_; -}; - std::unique_ptr Window::Create(Loop* loop, const std::string& title) { return std::make_unique(loop, title); } @@ -38,14 +31,14 @@ GTKWindow::GTKWindow(Loop* loop, const std::string& title) GTKWindow::~GTKWindow() { OnDestroy(); if (window_) { - gtk_widget_destroy(window_); + if (GTK_IS_WIDGET(window_)) gtk_widget_destroy(window_); window_ = nullptr; } } bool GTKWindow::Initialize() { return OnCreate(); } -void gtk_event_handler_(GtkWidget* widget, GdkEvent* event, gpointer data) { +gboolean gtk_event_handler(GtkWidget* widget, GdkEvent* event, gpointer data) { GTKWindow* window = reinterpret_cast(data); switch (event->type) { case GDK_OWNER_CHANGE: @@ -59,34 +52,80 @@ void gtk_event_handler_(GtkWidget* widget, GdkEvent* event, gpointer data) { window->HandleKeyboard(&(event->key)); break; case GDK_SCROLL: - case GDK_BUTTON_PRESS: case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_BUTTON_RELEASE: window->HandleMouse(&(event->any)); break; case GDK_FOCUS_CHANGE: window->HandleWindowFocus(&(event->focus_change)); break; case GDK_CONFIGURE: - window->HandleWindowResize(&(event->configure)); + // Only handle the event for the drawing area so we don't save + // a width and height that includes the menu bar on the full window + if (event->configure.window == + gtk_widget_get_window(window->drawing_area_)) { + window->HandleWindowResize(&(event->configure)); + } + break; default: // Do nothing - return; + break; } + // Propagate the event to other handlers + return GDK_EVENT_PROPAGATE; +} + +gboolean draw_callback(GtkWidget* widget, GdkFrameClock* frame_clock, + gpointer data) { + GTKWindow* window = reinterpret_cast(data); + window->HandleWindowPaint(); + return G_SOURCE_CONTINUE; +} + +gboolean close_callback(GtkWidget* widget, gpointer data) { + GTKWindow* window = reinterpret_cast(data); + window->Close(); + return G_SOURCE_CONTINUE; } void GTKWindow::Create() { + // GTK optionally allows passing argv and argc here for parsing gtk specific + // options. We won't bother window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window_), (gchar*)title_.c_str()); - gtk_window_set_default_size(GTK_WINDOW(window_), 1280, 720); - gtk_widget_show_all(window_); - g_signal_connect(G_OBJECT(window_), "destroy", G_CALLBACK(gtk_main_quit), - NULL); - g_signal_connect(G_OBJECT(window_), "event", G_CALLBACK(gtk_event_handler_), + gtk_window_set_resizable(GTK_WINDOW(window_), true); + gtk_window_set_title(GTK_WINDOW(window_), title_.c_str()); + gtk_window_set_default_size(GTK_WINDOW(window_), width_, height_); + // Drawing area is where we will attach our vulkan/gl context + drawing_area_ = gtk_drawing_area_new(); + // tick callback is for the refresh rate of the window + gtk_widget_add_tick_callback(drawing_area_, draw_callback, + reinterpret_cast(this), nullptr); + // Attach our event handler to both the main window (for keystrokes) and the + // drawing area (for mouse input, resize event, etc) + g_signal_connect(G_OBJECT(drawing_area_), "event", + G_CALLBACK(gtk_event_handler), reinterpret_cast(this)); GdkDisplay* gdk_display = gtk_widget_get_display(window_); assert(GDK_IS_X11_DISPLAY(gdk_display)); connection_ = XGetXCBConnection(gdk_x11_display_get_xdisplay(gdk_display)); + + g_signal_connect(G_OBJECT(window_), "event", G_CALLBACK(gtk_event_handler), + reinterpret_cast(this)); + // When the window manager kills the window (ie, the user hits X) + g_signal_connect(G_OBJECT(window_), "destroy", G_CALLBACK(close_callback), + reinterpret_cast(this)); + // Enable only keyboard events (so no mouse) for the top window + gtk_widget_set_events(window_, GDK_KEY_PRESS | GDK_KEY_RELEASE); + // Enable all events for the drawing area + gtk_widget_add_events(drawing_area_, GDK_ALL_EVENTS_MASK); + // Place the drawing area in a container (which later will hold the menu) + // then let it fill the whole area + box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_end(GTK_BOX(box_), drawing_area_, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(window_), box_); + gtk_widget_show_all(window_); } bool GTKWindow::OnCreate() { @@ -128,7 +167,6 @@ void GTKWindow::ToggleFullscreen(bool fullscreen) { } fullscreen_ = fullscreen; - if (fullscreen) { gtk_window_fullscreen(GTK_WINDOW(window_)); } else { @@ -145,7 +183,6 @@ void GTKWindow::set_bordered(bool enabled) { // Don't screw with the borders if we're fullscreen. return; } - gtk_window_set_decorated(GTK_WINDOW(window_), enabled); } @@ -176,31 +213,26 @@ void GTKWindow::set_focus(bool value) { } void GTKWindow::Resize(int32_t width, int32_t height) { + width_ = width; + height_ = height; gtk_window_resize(GTK_WINDOW(window_), width, height); } void GTKWindow::Resize(int32_t left, int32_t top, int32_t right, int32_t bottom) { - // TODO(dougvj) Verify that this is the desired behavior from this call + int32_t width = right - left; + int32_t height = bottom - top; + width_ = width; + height_ = height; gtk_window_move(GTK_WINDOW(window_), left, top); - gtk_window_resize(GTK_WINDOW(window_), left - right, top - bottom); + gtk_window_resize(GTK_WINDOW(window_), width, height); } -void GTKWindow::OnResize(UIEvent* e) { - int32_t width; - int32_t height; - gtk_window_get_size(GTK_WINDOW(window_), &width, &height); - if (width != width_ || height != height_) { - width_ = width; - height_ = height; - Layout(); - } - super::OnResize(e); -} +void GTKWindow::OnResize(UIEvent* e) { super::OnResize(e); } void GTKWindow::Invalidate() { + // gtk_widget_queue_draw(drawing_area_); super::Invalidate(); - // TODO(dougvj) I am not sure what this is supposed to do } void GTKWindow::Close() { @@ -214,14 +246,15 @@ void GTKWindow::Close() { void GTKWindow::OnMainMenuChange() { // We need to store the old handle for detachment - static GtkWidget* box = nullptr; + static int count = 0; auto main_menu = reinterpret_cast(main_menu_.get()); - if (main_menu && !is_fullscreen()) { - if (box) gtk_widget_destroy(box); - GtkWidget* menu = main_menu->handle(); - box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); - gtk_box_pack_start(GTK_BOX(box), menu, FALSE, FALSE, 3); - gtk_container_add(GTK_CONTAINER(window_), box); + if (main_menu && main_menu->handle()) { + if (!is_fullscreen()) { + gtk_box_pack_start(GTK_BOX(box_), main_menu->handle(), FALSE, FALSE, 0); + gtk_widget_show_all(window_); + } else { + gtk_container_remove(GTK_CONTAINER(box_), main_menu->handle()); + } } } @@ -239,9 +272,22 @@ bool GTKWindow::HandleWindowOwnerChange(GdkEventOwnerChange* event) { return false; } +bool GTKWindow::HandleWindowPaint() { + auto e = UIEvent(this); + OnPaint(&e); + return true; +} + bool GTKWindow::HandleWindowResize(GdkEventConfigure* event) { if (event->type == GDK_CONFIGURE) { + int32_t width = event->width; + int32_t height = event->height; auto e = UIEvent(this); + if (width != width_ || height != height_) { + width_ = width; + height_ = height; + Layout(); + } OnResize(&e); return true; } @@ -356,18 +402,20 @@ bool GTKWindow::HandleKeyboard(GdkEventKey* event) { bool ctrl_pressed = modifiers & GDK_CONTROL_MASK; bool alt_pressed = modifiers & GDK_META_MASK; bool super_pressed = modifiers & GDK_SUPER_MASK; + uint32_t key_char = gdk_keyval_to_unicode(event->keyval); auto e = KeyEvent(this, event->hardware_keycode, 1, event->type == GDK_KEY_RELEASE, shift_pressed, ctrl_pressed, alt_pressed, super_pressed); switch (event->type) { case GDK_KEY_PRESS: OnKeyDown(&e); + if (key_char > 0) { + OnKeyChar(&e); + } break; case GDK_KEY_RELEASE: OnKeyUp(&e); break; - // TODO(dougvj) GDK doesn't have a KEY CHAR event, so we will have to - // figure out its equivalent here to call OnKeyChar(&e); default: return false; } @@ -381,23 +429,35 @@ std::unique_ptr MenuItem::Create(Type type, return std::make_unique(type, text, hotkey, callback); } -static void _menu_activate_callback(GtkWidget* menu, gpointer data) { - auto fn = reinterpret_cast(data); - fn->Call(); - delete fn; +static void _menu_activate_callback(GtkWidget* gtk_menu, gpointer data) { + GTKMenuItem* menu = reinterpret_cast(data); + menu->Activate(); +} + +void GTKMenuItem::Activate() { + try { + callback_(); + } catch (const std::bad_function_call& e) { + // Ignore + } } GTKMenuItem::GTKMenuItem(Type type, const std::string& text, const std::string& hotkey, std::function callback) : MenuItem(type, text, hotkey, std::move(callback)) { + std::string label = text; + // TODO(dougvj) Would we ever need to escape underscores? + // Replace & with _ for gtk to see the memonic + std::replace(label.begin(), label.end(), '&', '_'); + const gchar* gtk_label = reinterpret_cast(label.c_str()); switch (type) { case MenuItem::Type::kNormal: default: menu_ = gtk_menu_bar_new(); break; case MenuItem::Type::kPopup: - menu_ = gtk_menu_item_new_with_label((gchar*)text.c_str()); + menu_ = gtk_menu_item_new_with_mnemonic(gtk_label); break; case MenuItem::Type::kSeparator: menu_ = gtk_separator_menu_item_new(); @@ -407,12 +467,12 @@ GTKMenuItem::GTKMenuItem(Type type, const std::string& text, if (!hotkey.empty()) { full_name += "\t" + hotkey; } - menu_ = gtk_menu_item_new_with_label((gchar*)full_name.c_str()); + menu_ = gtk_menu_item_new_with_mnemonic(gtk_label); break; } if (GTK_IS_MENU_ITEM(menu_)) g_signal_connect(menu_, "activate", G_CALLBACK(_menu_activate_callback), - (gpointer) new FnWrapper(callback)); + (gpointer)this); } GTKMenuItem::~GTKMenuItem() { @@ -460,4 +520,5 @@ void GTKMenuItem::OnChildRemoved(MenuItem* generic_child_item) { } } // namespace ui + } // namespace xe diff --git a/src/xenia/ui/window_gtk.h b/src/xenia/ui/window_gtk.h index ce28cfcd0..fba1886a3 100644 --- a/src/xenia/ui/window_gtk.h +++ b/src/xenia/ui/window_gtk.h @@ -34,7 +34,7 @@ class GTKWindow : public Window { NativePlatformHandle native_platform_handle() const override { return connection_; } - NativeWindowHandle native_handle() const override { return window_; } + NativeWindowHandle native_handle() const override { return drawing_area_; } void EnableMainMenu() override {} void DisableMainMenu() override {} @@ -75,15 +75,22 @@ class GTKWindow : public Window { private: void Create(); GtkWidget* window_; + GtkWidget* box_; + GtkWidget* drawing_area_; xcb_connection_t* connection_; - friend void gtk_event_handler_(GtkWidget*, GdkEvent*, gpointer); + // C Callback shims for GTK + friend gboolean gtk_event_handler(GtkWidget*, GdkEvent*, gpointer); + friend gboolean close_callback(GtkWidget*, gpointer); + friend gboolean draw_callback(GtkWidget*, GdkFrameClock*, gpointer); + bool HandleMouse(GdkEventAny* event); bool HandleKeyboard(GdkEventKey* event); bool HandleWindowResize(GdkEventConfigure* event); bool HandleWindowFocus(GdkEventFocus* event); bool HandleWindowVisibility(GdkEventVisibility* event); bool HandleWindowOwnerChange(GdkEventOwnerChange* event); + bool HandleWindowPaint(); bool closing_ = false; bool fullscreen_ = false; @@ -97,6 +104,7 @@ class GTKMenuItem : public MenuItem { GtkWidget* handle() { return menu_; } using MenuItem::OnSelected; + void Activate(); void EnableMenuItem(Window& window) override {} void DisableMenuItem(Window& window) override {} From 0b5ff8332e9248406d08e8fc24e68f0a4a9f6f43 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Mon, 21 Jan 2019 15:15:15 -0500 Subject: [PATCH 06/36] [ui gtk] Fix file dialog Use drawing area for vulkan but window for dialogs --- src/xenia/ui/vulkan/vulkan_context.cc | 2 +- src/xenia/ui/window_gtk.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index e39eada39..76101c6b7 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -71,7 +71,7 @@ bool VulkanContext::Initialize() { #elif XE_PLATFORM_LINUX #ifdef GDK_WINDOWING_X11 GtkWidget* window_handle = - static_cast(target_window_->native_handle()); + dynamic_cast(target_window_)->native_window_handle(); xcb_window_t window = gdk_x11_window_get_xid(gtk_widget_get_window(window_handle)); VkXcbSurfaceCreateInfoKHR create_info; diff --git a/src/xenia/ui/window_gtk.h b/src/xenia/ui/window_gtk.h index fba1886a3..27a4c08b0 100644 --- a/src/xenia/ui/window_gtk.h +++ b/src/xenia/ui/window_gtk.h @@ -34,7 +34,8 @@ class GTKWindow : public Window { NativePlatformHandle native_platform_handle() const override { return connection_; } - NativeWindowHandle native_handle() const override { return drawing_area_; } + NativeWindowHandle native_handle() const override { return window_; } + GtkWidget* native_window_handle() const { return drawing_area_; } void EnableMainMenu() override {} void DisableMainMenu() override {} From 271c91e1157ef2e788a1d365a9860348d27204e1 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 27 Jan 2019 11:39:21 -0500 Subject: [PATCH 07/36] [ui gtk] Fix init, resize and destroy Prevent Vulkan Swap before display context is assigned. Prevent resize while fullscreen (like in windows impl). Use superclass Resize implementation to reduce code duplication. Remove recursive call to GTKWindow::Close(). Destroy xcb window after superclass Close(). Set hwnd to null after closing on windows implementation. --- .../gpu/vulkan/vulkan_graphics_system.cc | 2 +- src/xenia/ui/window_gtk.cc | 23 +++++++++++-------- src/xenia/ui/window_win.cc | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index 0980e7f9e..2a9e16a7a 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -260,7 +260,7 @@ VulkanGraphicsSystem::CreateCommandProcessor() { } void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) { - if (!command_processor_) { + if (!command_processor_ || !display_context_) { return; } diff --git a/src/xenia/ui/window_gtk.cc b/src/xenia/ui/window_gtk.cc index a785af2df..a53aacabb 100644 --- a/src/xenia/ui/window_gtk.cc +++ b/src/xenia/ui/window_gtk.cc @@ -138,8 +138,6 @@ void GTKWindow::OnDestroy() { super::OnDestroy(); } void GTKWindow::OnClose() { if (!closing_ && window_) { closing_ = true; - gtk_widget_destroy(window_); - window_ = nullptr; } super::OnClose(); } @@ -213,19 +211,23 @@ void GTKWindow::set_focus(bool value) { } void GTKWindow::Resize(int32_t width, int32_t height) { - width_ = width; - height_ = height; + if (is_fullscreen()) { + // Cannot resize while in fullscreen. + return; + } gtk_window_resize(GTK_WINDOW(window_), width, height); + super::Resize(width, height); } void GTKWindow::Resize(int32_t left, int32_t top, int32_t right, int32_t bottom) { - int32_t width = right - left; - int32_t height = bottom - top; - width_ = width; - height_ = height; + if (is_fullscreen()) { + // Cannot resize while in fullscreen. + return; + } gtk_window_move(GTK_WINDOW(window_), left, top); - gtk_window_resize(GTK_WINDOW(window_), width, height); + gtk_window_resize(GTK_WINDOW(window_), right - left, bottom - top); + super::Resize(left, top, right, bottom); } void GTKWindow::OnResize(UIEvent* e) { super::OnResize(e); } @@ -240,8 +242,9 @@ void GTKWindow::Close() { return; } closing_ = true; - Close(); OnClose(); + gtk_widget_destroy(window_); + window_ = nullptr; } void GTKWindow::OnMainMenuChange() { diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 954502364..853f14030 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -446,6 +446,7 @@ void Win32Window::Close() { closing_ = true; OnClose(); DestroyWindow(hwnd_); + hwnd_ = 0; } void Win32Window::OnMainMenuChange() { From 0380067105597ae8a1cae16d3223de7364e9eaf4 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 21 Aug 2019 23:36:49 +0200 Subject: [PATCH 08/36] [linux vulkan] Remove linking directly with vulkan Using volk means vulkan linking is done at runtime with the dl library. --- src/xenia/app/premake5.lua | 1 - src/xenia/gpu/vulkan/premake5.lua | 2 -- src/xenia/ui/vulkan/premake5.lua | 1 - 3 files changed, 4 deletions(-) diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 8d836ff43..fcfacf499 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -74,7 +74,6 @@ project("xenia-app") "X11", "xcb", "X11-xcb", - "vulkan", "SDL2", }) diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index c1437995f..8c5573ae7 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -73,7 +73,6 @@ project("xenia-gpu-vulkan-trace-viewer") "xcb", "X11-xcb", "GL", - "vulkan", }) filter("platforms:Windows") @@ -142,7 +141,6 @@ project("xenia-gpu-vulkan-trace-dump") "xcb", "X11-xcb", "GL", - "vulkan", }) filter("platforms:Windows") diff --git a/src/xenia/ui/vulkan/premake5.lua b/src/xenia/ui/vulkan/premake5.lua index d93f98af6..c24c230c5 100644 --- a/src/xenia/ui/vulkan/premake5.lua +++ b/src/xenia/ui/vulkan/premake5.lua @@ -57,5 +57,4 @@ project("xenia-ui-window-vulkan-demo") "xcb", "X11-xcb", "GL", - "vulkan", }) From 005e0e21c13172d6877f1873f3e9f5d4bd583216 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 21 Aug 2019 23:41:35 +0200 Subject: [PATCH 09/36] [linux graphics] Remove GLEW OpenGL is not used in the whole stack. All references are removed. --- src/xenia/gpu/vulkan/premake5.lua | 2 -- src/xenia/ui/vulkan/premake5.lua | 1 - 2 files changed, 3 deletions(-) diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index 8c5573ae7..a1eb189d5 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -72,7 +72,6 @@ project("xenia-gpu-vulkan-trace-viewer") "X11", "xcb", "X11-xcb", - "GL", }) filter("platforms:Windows") @@ -140,7 +139,6 @@ project("xenia-gpu-vulkan-trace-dump") "X11", "xcb", "X11-xcb", - "GL", }) filter("platforms:Windows") diff --git a/src/xenia/ui/vulkan/premake5.lua b/src/xenia/ui/vulkan/premake5.lua index c24c230c5..6d2137567 100644 --- a/src/xenia/ui/vulkan/premake5.lua +++ b/src/xenia/ui/vulkan/premake5.lua @@ -56,5 +56,4 @@ project("xenia-ui-window-vulkan-demo") "X11", "xcb", "X11-xcb", - "GL", }) From 692e329e9c9bf6cc8ac7c1b260ad5f34ce58106c Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 11 Jul 2021 22:56:01 +0300 Subject: [PATCH 10/36] [Vulkan] Load Vulkan manually for more lifetime and extension control --- premake5.lua | 1 - src/xenia/app/premake5.lua | 1 - src/xenia/gpu/vulkan/buffer_cache.cc | 83 ++++--- src/xenia/gpu/vulkan/pipeline_cache.cc | 132 ++++++----- src/xenia/gpu/vulkan/premake5.lua | 3 - src/xenia/gpu/vulkan/render_cache.cc | 187 +++++++++------ src/xenia/gpu/vulkan/texture_cache.cc | 101 ++++---- .../gpu/vulkan/vulkan_command_processor.cc | 167 +++++++------ .../gpu/vulkan/vulkan_graphics_system.cc | 80 ++++--- src/xenia/gpu/vulkan/vulkan_shader.cc | 9 +- src/xenia/hid/premake5.lua | 1 - src/xenia/ui/vulkan/blitter.cc | 96 ++++---- src/xenia/ui/vulkan/circular_buffer.cc | 43 ++-- src/xenia/ui/vulkan/fenced_pools.cc | 37 +-- src/xenia/ui/vulkan/fenced_pools.h | 26 ++- src/xenia/ui/vulkan/functions/device_1_0.inc | 83 +++++++ .../functions/device_amd_shader_info.inc | 2 + .../functions/device_ext_debug_marker.inc | 5 + .../vulkan/functions/device_khr_swapchain.inc | 6 + .../ui/vulkan/functions/instance_1_0.inc | 13 ++ .../functions/instance_ext_debug_report.inc | 3 + .../instance_khr_android_surface.inc | 2 + .../vulkan/functions/instance_khr_surface.inc | 6 + .../functions/instance_khr_win32_surface.inc | 2 + .../functions/instance_khr_xcb_surface.inc | 2 + src/xenia/ui/vulkan/premake5.lua | 2 +- src/xenia/ui/vulkan/vulkan.h | 28 ++- src/xenia/ui/vulkan/vulkan_context.cc | 32 +-- src/xenia/ui/vulkan/vulkan_device.cc | 82 ++++--- src/xenia/ui/vulkan/vulkan_device.h | 24 +- .../ui/vulkan/vulkan_immediate_drawer.cc | 219 ++++++++++-------- src/xenia/ui/vulkan/vulkan_instance.cc | 198 +++++++++++----- src/xenia/ui/vulkan/vulkan_instance.h | 40 ++++ src/xenia/ui/vulkan/vulkan_mem_alloc.h | 38 +-- src/xenia/ui/vulkan/vulkan_swap_chain.cc | 169 +++++++------- src/xenia/ui/vulkan/vulkan_util.h | 58 +++-- third_party/volk | 1 - third_party/volk.lua | 30 --- 38 files changed, 1224 insertions(+), 788 deletions(-) create mode 100644 src/xenia/ui/vulkan/functions/device_1_0.inc create mode 100644 src/xenia/ui/vulkan/functions/device_amd_shader_info.inc create mode 100644 src/xenia/ui/vulkan/functions/device_ext_debug_marker.inc create mode 100644 src/xenia/ui/vulkan/functions/device_khr_swapchain.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_1_0.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_ext_debug_report.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_khr_android_surface.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_khr_surface.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_khr_win32_surface.inc create mode 100644 src/xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc delete mode 160000 third_party/volk delete mode 100644 third_party/volk.lua diff --git a/premake5.lua b/premake5.lua index f5d6d90b8..e44f81c71 100644 --- a/premake5.lua +++ b/premake5.lua @@ -231,7 +231,6 @@ workspace("xenia") include("third_party/mspack.lua") include("third_party/snappy.lua") include("third_party/spirv-tools.lua") - include("third_party/volk.lua") include("third_party/xxhash.lua") if not os.istarget("android") then diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 8d836ff43..a99fec0cc 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -43,7 +43,6 @@ project("xenia-app") "mspack", "snappy", "spirv-tools", - "volk", "xxhash", }) defines({ diff --git a/src/xenia/gpu/vulkan/buffer_cache.cc b/src/xenia/gpu/vulkan/buffer_cache.cc index 31aed6982..a02d9b54e 100644 --- a/src/xenia/gpu/vulkan/buffer_cache.cc +++ b/src/xenia/gpu/vulkan/buffer_cache.cc @@ -120,7 +120,7 @@ VkResult BufferCache::Initialize() { // Create a memory allocator for textures. VmaVulkanFunctions vulkan_funcs = {}; - ui::vulkan::FillVMAVulkanFunctions(&vulkan_funcs); + ui::vulkan::FillVMAVulkanFunctions(&vulkan_funcs, *device_); VmaAllocatorCreateInfo alloc_info = { 0, *device_, *device_, 0, 0, nullptr, nullptr, 0, nullptr, &vulkan_funcs, @@ -144,7 +144,8 @@ VkResult BufferCache::Initialize() { return VK_SUCCESS; } -VkResult xe::gpu::vulkan::BufferCache::CreateVertexDescriptorPool() { +VkResult BufferCache::CreateVertexDescriptorPool() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; std::vector pool_sizes; @@ -170,8 +171,8 @@ VkResult xe::gpu::vulkan::BufferCache::CreateVertexDescriptorPool() { 1, &binding, }; - status = vkCreateDescriptorSetLayout(*device_, &layout_info, nullptr, - &vertex_descriptor_set_layout_); + status = dfn.vkCreateDescriptorSetLayout(*device_, &layout_info, nullptr, + &vertex_descriptor_set_layout_); if (status != VK_SUCCESS) { return status; } @@ -179,14 +180,16 @@ VkResult xe::gpu::vulkan::BufferCache::CreateVertexDescriptorPool() { return VK_SUCCESS; } -void xe::gpu::vulkan::BufferCache::FreeVertexDescriptorPool() { +void BufferCache::FreeVertexDescriptorPool() { vertex_descriptor_pool_.reset(); - VK_SAFE_DESTROY(vkDestroyDescriptorSetLayout, *device_, - vertex_descriptor_set_layout_, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, *device_, + vertex_descriptor_set_layout_); } VkResult BufferCache::CreateConstantDescriptorSet() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Descriptor pool used for all of our cached descriptors. @@ -204,8 +207,8 @@ VkResult BufferCache::CreateConstantDescriptorSet() { pool_sizes[0].descriptorCount = 2; transient_descriptor_pool_info.poolSizeCount = 1; transient_descriptor_pool_info.pPoolSizes = pool_sizes; - status = vkCreateDescriptorPool(*device_, &transient_descriptor_pool_info, - nullptr, &constant_descriptor_pool_); + status = dfn.vkCreateDescriptorPool(*device_, &transient_descriptor_pool_info, + nullptr, &constant_descriptor_pool_); if (status != VK_SUCCESS) { return status; } @@ -237,9 +240,9 @@ VkResult BufferCache::CreateConstantDescriptorSet() { descriptor_set_layout_info.bindingCount = static_cast(xe::countof(bindings)); descriptor_set_layout_info.pBindings = bindings; - status = - vkCreateDescriptorSetLayout(*device_, &descriptor_set_layout_info, - nullptr, &constant_descriptor_set_layout_); + status = dfn.vkCreateDescriptorSetLayout(*device_, + &descriptor_set_layout_info, nullptr, + &constant_descriptor_set_layout_); if (status != VK_SUCCESS) { return status; } @@ -253,8 +256,8 @@ VkResult BufferCache::CreateConstantDescriptorSet() { set_alloc_info.descriptorPool = constant_descriptor_pool_; set_alloc_info.descriptorSetCount = 1; set_alloc_info.pSetLayouts = &constant_descriptor_set_layout_; - status = vkAllocateDescriptorSets(*device_, &set_alloc_info, - &constant_descriptor_set_); + status = dfn.vkAllocateDescriptorSets(*device_, &set_alloc_info, + &constant_descriptor_set_); if (status != VK_SUCCESS) { return status; } @@ -286,22 +289,24 @@ VkResult BufferCache::CreateConstantDescriptorSet() { fragment_uniform_binding_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; fragment_uniform_binding_write.pBufferInfo = &buffer_info; - vkUpdateDescriptorSets(*device_, 2, descriptor_writes, 0, nullptr); + dfn.vkUpdateDescriptorSets(*device_, 2, descriptor_writes, 0, nullptr); return VK_SUCCESS; } void BufferCache::FreeConstantDescriptorSet() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + if (constant_descriptor_set_) { - vkFreeDescriptorSets(*device_, constant_descriptor_pool_, 1, - &constant_descriptor_set_); + dfn.vkFreeDescriptorSets(*device_, constant_descriptor_pool_, 1, + &constant_descriptor_set_); constant_descriptor_set_ = nullptr; } - VK_SAFE_DESTROY(vkDestroyDescriptorSetLayout, *device_, - constant_descriptor_set_layout_, nullptr); - VK_SAFE_DESTROY(vkDestroyDescriptorPool, *device_, constant_descriptor_pool_, - nullptr); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, *device_, + constant_descriptor_set_layout_); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, *device_, + constant_descriptor_pool_); } void BufferCache::Shutdown() { @@ -314,7 +319,9 @@ void BufferCache::Shutdown() { FreeVertexDescriptorPool(); transient_buffer_->Shutdown(); - VK_SAFE_DESTROY(vkFreeMemory, *device_, gpu_memory_pool_, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + ui::vulkan::DestroyAndNullHandle(dfn.vkFreeMemory, *device_, + gpu_memory_pool_); } std::pair BufferCache::UploadConstantRegisters( @@ -361,9 +368,10 @@ std::pair BufferCache::UploadConstantRegisters( offset, kConstantRegisterUniformRange, }; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 1, - &barrier, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 1, + &barrier, 0, nullptr); return {offset, offset}; @@ -476,9 +484,10 @@ std::pair BufferCache::UploadIndexBuffer( offset, source_length, }; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, - &barrier, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1, + &barrier, 0, nullptr); return {transient_buffer_->gpu_buffer(), offset}; } @@ -543,9 +552,10 @@ std::pair BufferCache::UploadVertexBuffer( offset, upload_size, }; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr, 1, - &barrier, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr, + 1, &barrier, 0, nullptr); CacheTransientData(upload_base, upload_size, offset); return {transient_buffer_->gpu_buffer(), offset + source_offset}; @@ -687,7 +697,8 @@ VkDescriptorSet BufferCache::PrepareVertexSet( }; } - vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); vertex_sets_[hash] = set; return set; } @@ -760,13 +771,15 @@ void BufferCache::CacheTransientData(uint32_t guest_address, } void BufferCache::Flush(VkCommandBuffer command_buffer) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // If we are flushing a big enough chunk queue up an event. // We don't want to do this for everything but often enough so that we won't // run out of space. if (true) { // VkEvent finish_event; - // vkCmdSetEvent(cmd_buffer, finish_event, - // VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); + // dfn.vkCmdSetEvent(cmd_buffer, finish_event, + // VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); } // Flush memory. @@ -777,7 +790,7 @@ void BufferCache::Flush(VkCommandBuffer command_buffer) { dirty_range.memory = transient_buffer_->gpu_memory(); dirty_range.offset = 0; dirty_range.size = transient_buffer_->capacity(); - vkFlushMappedMemoryRanges(*device_, 1, &dirty_range); + dfn.vkFlushMappedMemoryRanges(*device_, 1, &dirty_range); } void BufferCache::InvalidateCache() { diff --git a/src/xenia/gpu/vulkan/pipeline_cache.cc b/src/xenia/gpu/vulkan/pipeline_cache.cc index 9bf106a3b..4defbcf04 100644 --- a/src/xenia/gpu/vulkan/pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/pipeline_cache.cc @@ -45,6 +45,7 @@ VkResult PipelineCache::Initialize( VkDescriptorSetLayout uniform_descriptor_set_layout, VkDescriptorSetLayout texture_descriptor_set_layout, VkDescriptorSetLayout vertex_descriptor_set_layout) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Initialize the shared driver pipeline cache. @@ -57,8 +58,8 @@ VkResult PipelineCache::Initialize( pipeline_cache_info.flags = 0; pipeline_cache_info.initialDataSize = 0; pipeline_cache_info.pInitialData = nullptr; - status = vkCreatePipelineCache(*device_, &pipeline_cache_info, nullptr, - &pipeline_cache_); + status = dfn.vkCreatePipelineCache(*device_, &pipeline_cache_info, nullptr, + &pipeline_cache_); if (status != VK_SUCCESS) { return status; } @@ -95,8 +96,8 @@ VkResult PipelineCache::Initialize( pipeline_layout_info.pushConstantRangeCount = static_cast(xe::countof(push_constant_ranges)); pipeline_layout_info.pPushConstantRanges = push_constant_ranges; - status = vkCreatePipelineLayout(*device_, &pipeline_layout_info, nullptr, - &pipeline_layout_); + status = dfn.vkCreatePipelineLayout(*device_, &pipeline_layout_info, nullptr, + &pipeline_layout_); if (status != VK_SUCCESS) { return status; } @@ -112,8 +113,8 @@ VkResult PipelineCache::Initialize( static_cast(sizeof(line_quad_list_geom)); shader_module_info.pCode = reinterpret_cast(line_quad_list_geom); - status = vkCreateShaderModule(*device_, &shader_module_info, nullptr, - &geometry_shaders_.line_quad_list); + status = dfn.vkCreateShaderModule(*device_, &shader_module_info, nullptr, + &geometry_shaders_.line_quad_list); if (status != VK_SUCCESS) { return status; } @@ -123,8 +124,8 @@ VkResult PipelineCache::Initialize( shader_module_info.codeSize = static_cast(sizeof(point_list_geom)); shader_module_info.pCode = reinterpret_cast(point_list_geom); - status = vkCreateShaderModule(*device_, &shader_module_info, nullptr, - &geometry_shaders_.point_list); + status = dfn.vkCreateShaderModule(*device_, &shader_module_info, nullptr, + &geometry_shaders_.point_list); if (status != VK_SUCCESS) { return status; } @@ -134,8 +135,8 @@ VkResult PipelineCache::Initialize( shader_module_info.codeSize = static_cast(sizeof(quad_list_geom)); shader_module_info.pCode = reinterpret_cast(quad_list_geom); - status = vkCreateShaderModule(*device_, &shader_module_info, nullptr, - &geometry_shaders_.quad_list); + status = dfn.vkCreateShaderModule(*device_, &shader_module_info, nullptr, + &geometry_shaders_.quad_list); if (status != VK_SUCCESS) { return status; } @@ -145,8 +146,8 @@ VkResult PipelineCache::Initialize( shader_module_info.codeSize = static_cast(sizeof(rect_list_geom)); shader_module_info.pCode = reinterpret_cast(rect_list_geom); - status = vkCreateShaderModule(*device_, &shader_module_info, nullptr, - &geometry_shaders_.rect_list); + status = dfn.vkCreateShaderModule(*device_, &shader_module_info, nullptr, + &geometry_shaders_.rect_list); if (status != VK_SUCCESS) { return status; } @@ -156,8 +157,8 @@ VkResult PipelineCache::Initialize( shader_module_info.codeSize = static_cast(sizeof(dummy_frag)); shader_module_info.pCode = reinterpret_cast(dummy_frag); - status = vkCreateShaderModule(*device_, &shader_module_info, nullptr, - &dummy_pixel_shader_); + status = dfn.vkCreateShaderModule(*device_, &shader_module_info, nullptr, + &dummy_pixel_shader_); if (status != VK_SUCCESS) { return status; } @@ -171,34 +172,37 @@ VkResult PipelineCache::Initialize( void PipelineCache::Shutdown() { ClearCache(); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Destroy geometry shaders. if (geometry_shaders_.line_quad_list) { - vkDestroyShaderModule(*device_, geometry_shaders_.line_quad_list, nullptr); + dfn.vkDestroyShaderModule(*device_, geometry_shaders_.line_quad_list, + nullptr); geometry_shaders_.line_quad_list = nullptr; } if (geometry_shaders_.point_list) { - vkDestroyShaderModule(*device_, geometry_shaders_.point_list, nullptr); + dfn.vkDestroyShaderModule(*device_, geometry_shaders_.point_list, nullptr); geometry_shaders_.point_list = nullptr; } if (geometry_shaders_.quad_list) { - vkDestroyShaderModule(*device_, geometry_shaders_.quad_list, nullptr); + dfn.vkDestroyShaderModule(*device_, geometry_shaders_.quad_list, nullptr); geometry_shaders_.quad_list = nullptr; } if (geometry_shaders_.rect_list) { - vkDestroyShaderModule(*device_, geometry_shaders_.rect_list, nullptr); + dfn.vkDestroyShaderModule(*device_, geometry_shaders_.rect_list, nullptr); geometry_shaders_.rect_list = nullptr; } if (dummy_pixel_shader_) { - vkDestroyShaderModule(*device_, dummy_pixel_shader_, nullptr); + dfn.vkDestroyShaderModule(*device_, dummy_pixel_shader_, nullptr); dummy_pixel_shader_ = nullptr; } if (pipeline_layout_) { - vkDestroyPipelineLayout(*device_, pipeline_layout_, nullptr); + dfn.vkDestroyPipelineLayout(*device_, pipeline_layout_, nullptr); pipeline_layout_ = nullptr; } if (pipeline_cache_) { - vkDestroyPipelineCache(*device_, pipeline_cache_, nullptr); + dfn.vkDestroyPipelineCache(*device_, pipeline_cache_, nullptr); pipeline_cache_ = nullptr; } } @@ -274,9 +278,10 @@ PipelineCache::UpdateStatus PipelineCache::ConfigurePipeline( } void PipelineCache::ClearCache() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); // Destroy all pipelines. for (auto it : cached_pipelines_) { - vkDestroyPipeline(*device_, it.second, nullptr); + dfn.vkDestroyPipeline(*device_, it.second, nullptr); } cached_pipelines_.clear(); COUNT_profile_set("gpu/pipeline_cache/pipelines", 0); @@ -338,8 +343,9 @@ VkPipeline PipelineCache::GetPipeline(const RenderState* render_state, pipeline_info.basePipelineHandle = nullptr; pipeline_info.basePipelineIndex = -1; VkPipeline pipeline = nullptr; - auto result = vkCreateGraphicsPipelines(*device_, pipeline_cache_, 1, - &pipeline_info, nullptr, &pipeline); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + auto result = dfn.vkCreateGraphicsPipelines( + *device_, pipeline_cache_, 1, &pipeline_info, nullptr, &pipeline); if (result != VK_SUCCESS) { XELOGE("vkCreateGraphicsPipelines failed with code {}", result); assert_always(); @@ -415,9 +421,7 @@ static void DumpShaderStatisticsAMD(const VkShaderStatisticsInfoAMD& stats) { } void PipelineCache::DumpShaderDisasmAMD(VkPipeline pipeline) { - auto fn_GetShaderInfoAMD = (PFN_vkGetShaderInfoAMD)vkGetDeviceProcAddr( - *device_, "vkGetShaderInfoAMD"); - + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; size_t data_size = 0; @@ -425,18 +429,18 @@ void PipelineCache::DumpShaderDisasmAMD(VkPipeline pipeline) { data_size = sizeof(stats); // Vertex shader - status = fn_GetShaderInfoAMD(*device_, pipeline, VK_SHADER_STAGE_VERTEX_BIT, - VK_SHADER_INFO_TYPE_STATISTICS_AMD, &data_size, - &stats); + status = dfn.vkGetShaderInfoAMD( + *device_, pipeline, VK_SHADER_STAGE_VERTEX_BIT, + VK_SHADER_INFO_TYPE_STATISTICS_AMD, &data_size, &stats); if (status == VK_SUCCESS) { XELOGI("AMD Vertex Shader Statistics:"); DumpShaderStatisticsAMD(stats); } // Fragment shader - status = fn_GetShaderInfoAMD(*device_, pipeline, VK_SHADER_STAGE_FRAGMENT_BIT, - VK_SHADER_INFO_TYPE_STATISTICS_AMD, &data_size, - &stats); + status = dfn.vkGetShaderInfoAMD( + *device_, pipeline, VK_SHADER_STAGE_FRAGMENT_BIT, + VK_SHADER_INFO_TYPE_STATISTICS_AMD, &data_size, &stats); if (status == VK_SUCCESS) { XELOGI("AMD Fragment Shader Statistics:"); DumpShaderStatisticsAMD(stats); @@ -451,6 +455,8 @@ void PipelineCache::DumpShaderDisasmNV( // This code is super ugly. Update this when NVidia includes an official // way to dump shader disassembly. + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + VkPipelineCacheCreateInfo pipeline_cache_info; VkPipelineCache dummy_pipeline_cache; pipeline_cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; @@ -458,23 +464,24 @@ void PipelineCache::DumpShaderDisasmNV( pipeline_cache_info.flags = 0; pipeline_cache_info.initialDataSize = 0; pipeline_cache_info.pInitialData = nullptr; - auto status = vkCreatePipelineCache(*device_, &pipeline_cache_info, nullptr, - &dummy_pipeline_cache); + auto status = dfn.vkCreatePipelineCache(*device_, &pipeline_cache_info, + nullptr, &dummy_pipeline_cache); CheckResult(status, "vkCreatePipelineCache"); // Create a pipeline on the dummy cache and dump it. VkPipeline dummy_pipeline; - status = vkCreateGraphicsPipelines(*device_, dummy_pipeline_cache, 1, - &pipeline_info, nullptr, &dummy_pipeline); + status = + dfn.vkCreateGraphicsPipelines(*device_, dummy_pipeline_cache, 1, + &pipeline_info, nullptr, &dummy_pipeline); std::vector pipeline_data; size_t data_size = 0; - status = vkGetPipelineCacheData(*device_, dummy_pipeline_cache, &data_size, - nullptr); + status = dfn.vkGetPipelineCacheData(*device_, dummy_pipeline_cache, + &data_size, nullptr); if (status == VK_SUCCESS) { pipeline_data.resize(data_size); - vkGetPipelineCacheData(*device_, dummy_pipeline_cache, &data_size, - pipeline_data.data()); + dfn.vkGetPipelineCacheData(*device_, dummy_pipeline_cache, &data_size, + pipeline_data.data()); // Scan the data for the disassembly. std::string disasm_vp, disasm_fp; @@ -530,8 +537,8 @@ void PipelineCache::DumpShaderDisasmNV( disasm_fp); } - vkDestroyPipeline(*device_, dummy_pipeline, nullptr); - vkDestroyPipelineCache(*device_, dummy_pipeline_cache, nullptr); + dfn.vkDestroyPipeline(*device_, dummy_pipeline, nullptr); + dfn.vkDestroyPipelineCache(*device_, dummy_pipeline_cache, nullptr); } VkShaderModule PipelineCache::GetGeometryShader( @@ -575,6 +582,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, SCOPE_profile_cpu_f("gpu"); #endif // FINE_GRAINED_DRAW_SCOPES + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); auto& regs = set_dynamic_state_registers_; bool window_offset_dirty = SetShadowRegister(®s.pa_sc_window_offset, @@ -620,7 +628,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, scissor_rect.offset.y = ws_y - adj_y; scissor_rect.extent.width = std::max(ws_w + adj_x, 0); scissor_rect.extent.height = std::max(ws_h + adj_y, 0); - vkCmdSetScissor(command_buffer, 0, 1, &scissor_rect); + dfn.vkCmdSetScissor(command_buffer, 0, 1, &scissor_rect); } // VK_DYNAMIC_STATE_VIEWPORT @@ -713,7 +721,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, assert_true(viewport_rect.minDepth >= 0 && viewport_rect.minDepth <= 1); assert_true(viewport_rect.maxDepth >= -1 && viewport_rect.maxDepth <= 1); - vkCmdSetViewport(command_buffer, 0, 1, &viewport_rect); + dfn.vkCmdSetViewport(command_buffer, 0, 1, &viewport_rect); } // VK_DYNAMIC_STATE_DEPTH_BIAS @@ -767,13 +775,13 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, regs.pa_su_poly_offset_offset != depth_bias_offset_vulkan) { regs.pa_su_poly_offset_scale = depth_bias_scale_vulkan; regs.pa_su_poly_offset_offset = depth_bias_offset_vulkan; - vkCmdSetDepthBias(command_buffer, depth_bias_offset_vulkan, 0.0f, - depth_bias_scale_vulkan); + dfn.vkCmdSetDepthBias(command_buffer, depth_bias_offset_vulkan, 0.0f, + depth_bias_scale_vulkan); } } else if (full_update) { regs.pa_su_poly_offset_scale = 0.0f; regs.pa_su_poly_offset_offset = 0.0f; - vkCmdSetDepthBias(command_buffer, 0.0f, 0.0f, 0.0f); + dfn.vkCmdSetDepthBias(command_buffer, 0.0f, 0.0f, 0.0f); } // VK_DYNAMIC_STATE_BLEND_CONSTANTS @@ -787,7 +795,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, blend_constant_state_dirty |= SetShadowRegister(®s.rb_blend_rgba[3], XE_GPU_REG_RB_BLEND_ALPHA); if (blend_constant_state_dirty) { - vkCmdSetBlendConstants(command_buffer, regs.rb_blend_rgba); + dfn.vkCmdSetBlendConstants(command_buffer, regs.rb_blend_rgba); } bool stencil_state_dirty = full_update; @@ -799,16 +807,16 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, uint32_t stencil_write_mask = (regs.rb_stencilrefmask >> 16) & 0xFF; // VK_DYNAMIC_STATE_STENCIL_REFERENCE - vkCmdSetStencilReference(command_buffer, VK_STENCIL_FRONT_AND_BACK, - stencil_ref); + dfn.vkCmdSetStencilReference(command_buffer, VK_STENCIL_FRONT_AND_BACK, + stencil_ref); // VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK - vkCmdSetStencilCompareMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, - stencil_read_mask); + dfn.vkCmdSetStencilCompareMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, + stencil_read_mask); // VK_DYNAMIC_STATE_STENCIL_WRITE_MASK - vkCmdSetStencilWriteMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, - stencil_write_mask); + dfn.vkCmdSetStencilWriteMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, + stencil_write_mask); } bool push_constants_dirty = full_update || viewport_state_dirty; @@ -905,19 +913,19 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, push_constants.ps_param_gen = regs.sq_program_cntl.param_gen ? ps_param_gen : -1; - vkCmdPushConstants(command_buffer, pipeline_layout_, - VK_SHADER_STAGE_VERTEX_BIT | - VK_SHADER_STAGE_GEOMETRY_BIT | - VK_SHADER_STAGE_FRAGMENT_BIT, - 0, kSpirvPushConstantsSize, &push_constants); + dfn.vkCmdPushConstants(command_buffer, pipeline_layout_, + VK_SHADER_STAGE_VERTEX_BIT | + VK_SHADER_STAGE_GEOMETRY_BIT | + VK_SHADER_STAGE_FRAGMENT_BIT, + 0, kSpirvPushConstantsSize, &push_constants); } if (full_update) { // VK_DYNAMIC_STATE_LINE_WIDTH - vkCmdSetLineWidth(command_buffer, 1.0f); + dfn.vkCmdSetLineWidth(command_buffer, 1.0f); // VK_DYNAMIC_STATE_DEPTH_BOUNDS - vkCmdSetDepthBounds(command_buffer, 0.0f, 1.0f); + dfn.vkCmdSetDepthBounds(command_buffer, 0.0f, 1.0f); } return true; diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index c1437995f..12efb5174 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -8,7 +8,6 @@ project("xenia-gpu-vulkan") language("C++") links({ "fmt", - "volk", "xenia-base", "xenia-gpu", "xenia-ui", @@ -57,7 +56,6 @@ project("xenia-gpu-vulkan-trace-viewer") "mspack", "snappy", "spirv-tools", - "volk", "xxhash", }) defines({ @@ -126,7 +124,6 @@ project("xenia-gpu-vulkan-trace-dump") "mspack", "snappy", "spirv-tools", - "volk", "xxhash", }) defines({ diff --git a/src/xenia/gpu/vulkan/render_cache.cc b/src/xenia/gpu/vulkan/render_cache.cc index 1882c0082..b3dc4d389 100644 --- a/src/xenia/gpu/vulkan/render_cache.cc +++ b/src/xenia/gpu/vulkan/render_cache.cc @@ -105,8 +105,9 @@ class CachedFramebuffer { // Associated render pass VkRenderPass render_pass = nullptr; - CachedFramebuffer(VkDevice device, VkRenderPass render_pass, - uint32_t surface_width, uint32_t surface_height, + CachedFramebuffer(const ui::vulkan::VulkanDevice& device, + VkRenderPass render_pass, uint32_t surface_width, + uint32_t surface_height, CachedTileView* target_color_attachments[4], CachedTileView* target_depth_stencil_attachment); ~CachedFramebuffer(); @@ -116,7 +117,7 @@ class CachedFramebuffer { bool IsCompatible(const RenderConfiguration& desired_config) const; private: - VkDevice device_ = nullptr; + const ui::vulkan::VulkanDevice& device_; }; // Cached render passes based on register states. @@ -133,7 +134,8 @@ class CachedRenderPass { // Cache of framebuffers for the various tile attachments. std::vector cached_framebuffers; - CachedRenderPass(VkDevice device, const RenderConfiguration& desired_config); + CachedRenderPass(const ui::vulkan::VulkanDevice& device, + const RenderConfiguration& desired_config); ~CachedRenderPass(); VkResult Initialize(); @@ -141,7 +143,7 @@ class CachedRenderPass { bool IsCompatible(const RenderConfiguration& desired_config) const; private: - VkDevice device_ = nullptr; + const ui::vulkan::VulkanDevice& device_; }; CachedTileView::CachedTileView(ui::vulkan::VulkanDevice* device, @@ -150,14 +152,19 @@ CachedTileView::CachedTileView(ui::vulkan::VulkanDevice* device, : device_(device), key(std::move(view_key)) {} CachedTileView::~CachedTileView() { - VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view, nullptr); - VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view_depth, nullptr); - VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view_stencil, nullptr); - VK_SAFE_DESTROY(vkDestroyImage, *device_, image, nullptr); - VK_SAFE_DESTROY(vkFreeMemory, *device_, memory, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyImageView, *device_, + image_view); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyImageView, *device_, + image_view_depth); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyImageView, *device_, + image_view_stencil); + ui::vulkan::DestroyAndNullHandle(dfn.vkDestroyImage, *device_, image); + ui::vulkan::DestroyAndNullHandle(dfn.vkFreeMemory, *device_, memory); } VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Map format to Vulkan. @@ -230,7 +237,7 @@ VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { image_info.queueFamilyIndexCount = 0; image_info.pQueueFamilyIndices = nullptr; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - status = vkCreateImage(*device_, &image_info, nullptr, &image); + status = dfn.vkCreateImage(*device_, &image_info, nullptr, &image); if (status != VK_SUCCESS) { return status; } @@ -244,12 +251,12 @@ VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { uint32_t(key.msaa_samples), uint32_t(key.edram_format))); VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(*device_, image, &memory_requirements); + dfn.vkGetImageMemoryRequirements(*device_, image, &memory_requirements); // Bind to a newly allocated chunk. // TODO: Alias from a really big buffer? memory = device_->AllocateMemory(memory_requirements, 0); - status = vkBindImageMemory(*device_, image, memory, 0); + status = dfn.vkBindImageMemory(*device_, image, memory, 0); if (status != VK_SUCCESS) { return status; } @@ -276,7 +283,8 @@ VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; } - status = vkCreateImageView(*device_, &image_view_info, nullptr, &image_view); + status = + dfn.vkCreateImageView(*device_, &image_view_info, nullptr, &image_view); if (status != VK_SUCCESS) { return status; } @@ -284,15 +292,15 @@ VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { // Create separate depth/stencil views. if (key.color_or_depth == 0) { image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - status = vkCreateImageView(*device_, &image_view_info, nullptr, - &image_view_depth); + status = dfn.vkCreateImageView(*device_, &image_view_info, nullptr, + &image_view_depth); if (status != VK_SUCCESS) { return status; } image_view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; - status = vkCreateImageView(*device_, &image_view_info, nullptr, - &image_view_stencil); + status = dfn.vkCreateImageView(*device_, &image_view_info, nullptr, + &image_view_stencil); if (status != VK_SUCCESS) { return status; } @@ -319,19 +327,20 @@ VkResult CachedTileView::Initialize(VkCommandBuffer command_buffer) { image_barrier.subresourceRange.levelCount = 1; image_barrier.subresourceRange.baseArrayLayer = 0; image_barrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - key.color_or_depth - ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - : VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, - 0, 0, nullptr, 0, nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + key.color_or_depth + ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + : VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + 0, 0, nullptr, 0, nullptr, 1, &image_barrier); image_layout = image_barrier.newLayout; return VK_SUCCESS; } CachedFramebuffer::CachedFramebuffer( - VkDevice device, VkRenderPass render_pass, uint32_t surface_width, - uint32_t surface_height, CachedTileView* target_color_attachments[4], + const ui::vulkan::VulkanDevice& device, VkRenderPass render_pass, + uint32_t surface_width, uint32_t surface_height, + CachedTileView* target_color_attachments[4], CachedTileView* target_depth_stencil_attachment) : device_(device), width(surface_width), @@ -344,7 +353,10 @@ CachedFramebuffer::CachedFramebuffer( } CachedFramebuffer::~CachedFramebuffer() { - VK_SAFE_DESTROY(vkDestroyFramebuffer, device_, handle, nullptr); + if (handle != VK_NULL_HANDLE) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkDestroyFramebuffer(device_, handle, nullptr); + } } VkResult CachedFramebuffer::Initialize() { @@ -369,7 +381,8 @@ VkResult CachedFramebuffer::Initialize() { framebuffer_info.width = width; framebuffer_info.height = height; framebuffer_info.layers = 1; - return vkCreateFramebuffer(device_, &framebuffer_info, nullptr, &handle); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + return dfn.vkCreateFramebuffer(device_, &framebuffer_info, nullptr, &handle); } bool CachedFramebuffer::IsCompatible( @@ -414,7 +427,7 @@ bool CachedFramebuffer::IsCompatible( return true; } -CachedRenderPass::CachedRenderPass(VkDevice device, +CachedRenderPass::CachedRenderPass(const ui::vulkan::VulkanDevice& device, const RenderConfiguration& desired_config) : device_(device) { std::memcpy(&config, &desired_config, sizeof(config)); @@ -426,7 +439,10 @@ CachedRenderPass::~CachedRenderPass() { } cached_framebuffers.clear(); - VK_SAFE_DESTROY(vkDestroyRenderPass, device_, handle, nullptr); + if (handle != VK_NULL_HANDLE) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkDestroyRenderPass(device_, handle, nullptr); + } } VkResult CachedRenderPass::Initialize() { @@ -537,7 +553,8 @@ VkResult CachedRenderPass::Initialize() { render_pass_info.dependencyCount = 1; render_pass_info.pDependencies = dependencies; - return vkCreateRenderPass(device_, &render_pass_info, nullptr, &handle); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + return dfn.vkCreateRenderPass(device_, &render_pass_info, nullptr, &handle); } bool CachedRenderPass::IsCompatible( @@ -566,6 +583,7 @@ RenderCache::RenderCache(RegisterFile* register_file, RenderCache::~RenderCache() { Shutdown(); } VkResult RenderCache::Initialize() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Create the buffer we'll bind to our memory. @@ -579,7 +597,7 @@ VkResult RenderCache::Initialize() { buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buffer_info.queueFamilyIndexCount = 0; buffer_info.pQueueFamilyIndices = nullptr; - status = vkCreateBuffer(*device_, &buffer_info, nullptr, &edram_buffer_); + status = dfn.vkCreateBuffer(*device_, &buffer_info, nullptr, &edram_buffer_); CheckResult(status, "vkCreateBuffer"); if (status != VK_SUCCESS) { return status; @@ -588,7 +606,8 @@ VkResult RenderCache::Initialize() { // Query requirements for the buffer. // It should be 1:1. VkMemoryRequirements buffer_requirements; - vkGetBufferMemoryRequirements(*device_, edram_buffer_, &buffer_requirements); + dfn.vkGetBufferMemoryRequirements(*device_, edram_buffer_, + &buffer_requirements); assert_true(buffer_requirements.size == kEdramBufferCapacity); // Allocate EDRAM memory. @@ -600,7 +619,7 @@ VkResult RenderCache::Initialize() { } // Bind buffer to map our entire memory. - status = vkBindBufferMemory(*device_, edram_buffer_, edram_memory_, 0); + status = dfn.vkBindBufferMemory(*device_, edram_buffer_, edram_memory_, 0); CheckResult(status, "vkBindBufferMemory"); if (status != VK_SUCCESS) { return status; @@ -609,15 +628,16 @@ VkResult RenderCache::Initialize() { if (status == VK_SUCCESS) { // For debugging, upload a grid into the EDRAM buffer. uint32_t* gpu_data = nullptr; - status = vkMapMemory(*device_, edram_memory_, 0, buffer_requirements.size, - 0, reinterpret_cast(&gpu_data)); + status = + dfn.vkMapMemory(*device_, edram_memory_, 0, buffer_requirements.size, 0, + reinterpret_cast(&gpu_data)); if (status == VK_SUCCESS) { for (int i = 0; i < kEdramBufferCapacity / 4; i++) { gpu_data[i] = (i % 8) >= 4 ? 0xFF0000FF : 0xFFFFFFFF; } - vkUnmapMemory(*device_, edram_memory_); + dfn.vkUnmapMemory(*device_, edram_memory_); } } @@ -627,6 +647,8 @@ VkResult RenderCache::Initialize() { void RenderCache::Shutdown() { // TODO(benvanik): wait for idle. + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Dispose all render passes (and their framebuffers). for (auto render_pass : cached_render_passes_) { delete render_pass; @@ -641,11 +663,11 @@ void RenderCache::Shutdown() { // Release underlying EDRAM memory. if (edram_buffer_) { - vkDestroyBuffer(*device_, edram_buffer_, nullptr); + dfn.vkDestroyBuffer(*device_, edram_buffer_, nullptr); edram_buffer_ = nullptr; } if (edram_memory_) { - vkFreeMemory(*device_, edram_memory_, nullptr); + dfn.vkFreeMemory(*device_, edram_memory_, nullptr); edram_memory_ = nullptr; } } @@ -781,8 +803,9 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer, render_pass_begin_info.pClearValues = nullptr; // Begin the render pass. - vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, - VK_SUBPASS_CONTENTS_INLINE); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, + VK_SUBPASS_CONTENTS_INLINE); return ¤t_state_; } @@ -1019,6 +1042,8 @@ CachedTileView* RenderCache::FindOrCreateTileView( void RenderCache::UpdateTileView(VkCommandBuffer command_buffer, CachedTileView* view, bool load, bool insert_barrier) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + uint32_t tile_width = view->key.msaa_samples == uint16_t(xenos::MsaaSamples::k4X) ? 40 : 80; uint32_t tile_height = @@ -1043,9 +1068,9 @@ void RenderCache::UpdateTileView(VkCommandBuffer command_buffer, tile_height * view->key.color_or_depth ? 4 : 1; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1, - &barrier, 0, nullptr); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, + 1, &barrier, 0, nullptr); } // TODO(DrChat): Stencil copies. @@ -1061,11 +1086,12 @@ void RenderCache::UpdateTileView(VkCommandBuffer command_buffer, region.imageExtent = {view->key.tile_width * tile_width, view->key.tile_height * tile_height, 1}; if (load) { - vkCmdCopyBufferToImage(command_buffer, edram_buffer_, view->image, - VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); + dfn.vkCmdCopyBufferToImage(command_buffer, edram_buffer_, view->image, + VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); } else { - vkCmdCopyImageToBuffer(command_buffer, view->image, VK_IMAGE_LAYOUT_GENERAL, - edram_buffer_, 1, ®ion); + dfn.vkCmdCopyImageToBuffer(command_buffer, view->image, + VK_IMAGE_LAYOUT_GENERAL, edram_buffer_, 1, + ®ion); } } @@ -1085,7 +1111,8 @@ void RenderCache::EndRenderPass() { assert_not_null(current_command_buffer_); // End the render pass. - vkCmdEndRenderPass(current_command_buffer_); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdEndRenderPass(current_command_buffer_); // Copy all render targets back into our EDRAM buffer. // Don't bother waiting on this command to complete, as next render pass may @@ -1138,6 +1165,8 @@ void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, VkImageLayout image_layout, bool color_or_depth, VkOffset3D offset, VkExtent3D extents) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Transition the texture into a transfer destination layout. VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -1157,9 +1186,9 @@ void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, + 0, nullptr, 1, &image_barrier); } VkBufferMemoryBarrier buffer_barrier; @@ -1173,9 +1202,9 @@ void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, // TODO: Calculate this accurately (need texel size) buffer_barrier.size = extents.width * extents.height * 4; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1, - &buffer_barrier, 0, nullptr); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1, + &buffer_barrier, 0, nullptr); // Issue the copy command. // TODO(DrChat): Stencil copies. @@ -1188,8 +1217,8 @@ void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, region.imageSubresource = {0, 0, 0, 1}; region.imageSubresource.aspectMask = color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; - vkCmdCopyBufferToImage(command_buffer, edram_buffer_, image, image_layout, 1, - ®ion); + dfn.vkCmdCopyBufferToImage(command_buffer, edram_buffer_, image, image_layout, + 1, ®ion); // Transition the image back into its previous layout. if (image_layout != VK_IMAGE_LAYOUT_GENERAL && @@ -1197,9 +1226,9 @@ void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer, image_barrier.srcAccessMask = image_barrier.dstAccessMask; image_barrier.dstAccessMask = 0; std::swap(image_barrier.oldLayout, image_barrier.newLayout); - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, + 0, nullptr, 1, &image_barrier); } } @@ -1210,6 +1239,8 @@ void RenderCache::BlitToImage(VkCommandBuffer command_buffer, bool color_or_depth, uint32_t format, VkFilter filter, VkOffset3D offset, VkExtent3D extents) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + if (color_or_depth) { // Adjust similar formats for easier matching. format = static_cast( @@ -1252,9 +1283,9 @@ void RenderCache::BlitToImage(VkCommandBuffer command_buffer, color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &image_barrier); // If we overflow we'll lose the device here. // assert_true(extents.width <= key.tile_width * tile_width); @@ -1277,8 +1308,9 @@ void RenderCache::BlitToImage(VkCommandBuffer command_buffer, image_blit.dstOffsets[1] = {offset.x + int32_t(extents.width), offset.y + int32_t(extents.height), offset.z + int32_t(extents.depth)}; - vkCmdBlitImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, - image, image_layout, 1, &image_blit, filter); + dfn.vkCmdBlitImage(command_buffer, tile_view->image, + VK_IMAGE_LAYOUT_GENERAL, image, image_layout, 1, + &image_blit, filter); } else { VkImageResolve image_resolve; image_resolve.srcSubresource = {0, 0, 0, 1}; @@ -1292,8 +1324,9 @@ void RenderCache::BlitToImage(VkCommandBuffer command_buffer, image_resolve.dstOffset = offset; image_resolve.extent = extents; - vkCmdResolveImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL, - image, image_layout, 1, &image_resolve); + dfn.vkCmdResolveImage(command_buffer, tile_view->image, + VK_IMAGE_LAYOUT_GENERAL, image, image_layout, 1, + &image_resolve); } // Add another barrier on the tile view. @@ -1302,9 +1335,9 @@ void RenderCache::BlitToImage(VkCommandBuffer command_buffer, color_or_depth ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; std::swap(image_barrier.oldLayout, image_barrier.newLayout); - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &image_barrier); } void RenderCache::ClearEDRAMColor(VkCommandBuffer command_buffer, @@ -1339,8 +1372,9 @@ void RenderCache::ClearEDRAMColor(VkCommandBuffer command_buffer, std::memcpy(clear_value.float32, color, sizeof(float) * 4); // Issue a clear command - vkCmdClearColorImage(command_buffer, tile_view->image, - VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdClearColorImage(command_buffer, tile_view->image, + VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range); // Copy image back into EDRAM buffer // UpdateTileView(command_buffer, tile_view, false, false); @@ -1378,16 +1412,19 @@ void RenderCache::ClearEDRAMDepthStencil(VkCommandBuffer command_buffer, clear_value.stencil = stencil; // Issue a clear command - vkCmdClearDepthStencilImage(command_buffer, tile_view->image, - VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdClearDepthStencilImage(command_buffer, tile_view->image, + VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, + &range); // Copy image back into EDRAM buffer // UpdateTileView(command_buffer, tile_view, false, false); } void RenderCache::FillEDRAM(VkCommandBuffer command_buffer, uint32_t value) { - vkCmdFillBuffer(command_buffer, edram_buffer_, 0, kEdramBufferCapacity, - value); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdFillBuffer(command_buffer, edram_buffer_, 0, kEdramBufferCapacity, + value); } bool RenderCache::SetShadowRegister(uint32_t* dest, uint32_t register_name) { diff --git a/src/xenia/gpu/vulkan/texture_cache.cc b/src/xenia/gpu/vulkan/texture_cache.cc index 1aac2c80e..4e26d0aa2 100644 --- a/src/xenia/gpu/vulkan/texture_cache.cc +++ b/src/xenia/gpu/vulkan/texture_cache.cc @@ -22,6 +22,7 @@ #include "xenia/gpu/texture_info.h" #include "xenia/gpu/vulkan/texture_config.h" #include "xenia/gpu/vulkan/vulkan_gpu_flags.h" +#include "xenia/ui/vulkan/vulkan_instance.h" #include "xenia/ui/vulkan/vulkan_mem_alloc.h" DECLARE_bool(texture_dump); @@ -67,6 +68,7 @@ TextureCache::TextureCache(Memory* memory, RegisterFile* register_file, TextureCache::~TextureCache() { Shutdown(); } VkResult TextureCache::Initialize() { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Descriptor pool used for all of our cached descriptors. @@ -115,8 +117,8 @@ VkResult TextureCache::Initialize() { static_cast(xe::countof(bindings)); descriptor_set_layout_info.pBindings = bindings; status = - vkCreateDescriptorSetLayout(*device_, &descriptor_set_layout_info, - nullptr, &texture_descriptor_set_layout_); + dfn.vkCreateDescriptorSetLayout(*device_, &descriptor_set_layout_info, + nullptr, &texture_descriptor_set_layout_); if (status != VK_SUCCESS) { return status; } @@ -133,15 +135,15 @@ VkResult TextureCache::Initialize() { // Create a memory allocator for textures. VmaVulkanFunctions vulkan_funcs = {}; - ui::vulkan::FillVMAVulkanFunctions(&vulkan_funcs); + ui::vulkan::FillVMAVulkanFunctions(&vulkan_funcs, *device_); VmaAllocatorCreateInfo alloc_info = { 0, *device_, *device_, 0, 0, nullptr, nullptr, 0, nullptr, &vulkan_funcs, }; status = vmaCreateAllocator(&alloc_info, &mem_allocator_); if (status != VK_SUCCESS) { - vkDestroyDescriptorSetLayout(*device_, texture_descriptor_set_layout_, - nullptr); + dfn.vkDestroyDescriptorSetLayout(*device_, texture_descriptor_set_layout_, + nullptr); return status; } @@ -177,8 +179,9 @@ void TextureCache::Shutdown() { vmaDestroyAllocator(mem_allocator_); mem_allocator_ = nullptr; } - vkDestroyDescriptorSetLayout(*device_, texture_descriptor_set_layout_, - nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkDestroyDescriptorSetLayout(*device_, texture_descriptor_set_layout_, + nullptr); } TextureCache::Texture* TextureCache::AllocateTexture( @@ -229,9 +232,12 @@ TextureCache::Texture* TextureCache::AllocateTexture( image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + const ui::vulkan::VulkanInstance* instance = device_->instance(); + const ui::vulkan::VulkanInstance::InstanceFunctions& ifn = instance->ifn(); + // Check the device limits for the format before we create it. VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(*device_, format, &props); + ifn.vkGetPhysicalDeviceFormatProperties(*device_, format, &props); if ((props.optimalTilingFeatures & required_flags) != required_flags) { // Texture needs conversion on upload to a native format. XELOGE( @@ -257,7 +263,7 @@ TextureCache::Texture* TextureCache::AllocateTexture( } VkImageFormatProperties image_props; - vkGetPhysicalDeviceImageFormatProperties( + ifn.vkGetPhysicalDeviceImageFormatProperties( *device_, format, image_info.imageType, image_info.tiling, image_info.usage, image_info.flags, &image_props); @@ -308,8 +314,10 @@ TextureCache::Texture* TextureCache::AllocateTexture( } bool TextureCache::FreeTexture(Texture* texture) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + if (texture->in_flight_fence) { - VkResult status = vkGetFenceStatus(*device_, texture->in_flight_fence); + VkResult status = dfn.vkGetFenceStatus(*device_, texture->in_flight_fence); if (status != VK_SUCCESS && status != VK_ERROR_DEVICE_LOST) { // Texture still in flight. return false; @@ -317,11 +325,11 @@ bool TextureCache::FreeTexture(Texture* texture) { } if (texture->framebuffer) { - vkDestroyFramebuffer(*device_, texture->framebuffer, nullptr); + dfn.vkDestroyFramebuffer(*device_, texture->framebuffer, nullptr); } for (auto it = texture->views.begin(); it != texture->views.end();) { - vkDestroyImageView(*device_, (*it)->view, nullptr); + dfn.vkDestroyImageView(*device_, (*it)->view, nullptr); it = texture->views.erase(it); } @@ -692,7 +700,8 @@ TextureCache::TextureView* TextureCache::DemandView(Texture* texture, !is_cube ? 1 : 1 + texture->texture_info.depth; VkImageView view; - auto status = vkCreateImageView(*device_, &view_info, nullptr, &view); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + auto status = dfn.vkCreateImageView(*device_, &view_info, nullptr, &view); CheckResult(status, "vkCreateImageView"); if (status == VK_SUCCESS) { auto texture_view = new TextureView(); @@ -832,8 +841,9 @@ TextureCache::Sampler* TextureCache::Demand(const SamplerInfo& sampler_info) { sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; sampler_create_info.unnormalizedCoordinates = VK_FALSE; VkSampler vk_sampler; + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); status = - vkCreateSampler(*device_, &sampler_create_info, nullptr, &vk_sampler); + dfn.vkCreateSampler(*device_, &sampler_create_info, nullptr, &vk_sampler); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return nullptr; @@ -947,7 +957,8 @@ TextureCache::Texture* TextureCache::LookupAddress(uint32_t guest_address, void TextureCache::FlushPendingCommands(VkCommandBuffer command_buffer, VkFence completion_fence) { - auto status = vkEndCommandBuffer(command_buffer); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + auto status = dfn.vkEndCommandBuffer(command_buffer); CheckResult(status, "vkEndCommandBuffer"); VkSubmitInfo submit_info; @@ -958,27 +969,27 @@ void TextureCache::FlushPendingCommands(VkCommandBuffer command_buffer, if (device_queue_) { auto status = - vkQueueSubmit(device_queue_, 1, &submit_info, completion_fence); + dfn.vkQueueSubmit(device_queue_, 1, &submit_info, completion_fence); CheckResult(status, "vkQueueSubmit"); } else { std::lock_guard lock(device_->primary_queue_mutex()); - auto status = vkQueueSubmit(device_->primary_queue(), 1, &submit_info, - completion_fence); + auto status = dfn.vkQueueSubmit(device_->primary_queue(), 1, &submit_info, + completion_fence); CheckResult(status, "vkQueueSubmit"); } - vkWaitForFences(*device_, 1, &completion_fence, VK_TRUE, -1); + dfn.vkWaitForFences(*device_, 1, &completion_fence, VK_TRUE, -1); staging_buffer_.Scavenge(); - vkResetFences(*device_, 1, &completion_fence); + dfn.vkResetFences(*device_, 1, &completion_fence); // Reset the command buffer and put it back into the recording state. - vkResetCommandBuffer(command_buffer, 0); + dfn.vkResetCommandBuffer(command_buffer, 0); VkCommandBufferBeginInfo begin_info; std::memset(&begin_info, 0, sizeof(begin_info)); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - vkBeginCommandBuffer(command_buffer, &begin_info); + dfn.vkBeginCommandBuffer(command_buffer, &begin_info); } bool TextureCache::ConvertTexture(uint8_t* dest, VkBufferImageCopy* copy_region, @@ -1155,6 +1166,8 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, TextureDump(src, unpack_buffer, unpack_length); } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Transition the texture into a transfer destination layout. VkImageMemoryBarrier barrier; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -1181,9 +1194,9 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, barrier.subresourceRange.layerCount = copy_regions[0].imageSubresource.layerCount; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, - nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); // Now move the converted texture into the destination. if (dest->format == VK_FORMAT_D16_UNORM_S8_UINT || @@ -1195,19 +1208,19 @@ bool TextureCache::UploadTexture(VkCommandBuffer command_buffer, copy_regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; } - vkCmdCopyBufferToImage(command_buffer, staging_buffer_.gpu_buffer(), - dest->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - copy_region_count, copy_regions.data()); + dfn.vkCmdCopyBufferToImage(command_buffer, staging_buffer_.gpu_buffer(), + dest->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + copy_region_count, copy_regions.data()); // Now transition the texture into a shader readonly source. barrier.srcAccessMask = barrier.dstAccessMask; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; barrier.oldLayout = barrier.newLayout; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); dest->image_layout = barrier.newLayout; return true; @@ -1297,6 +1310,7 @@ uint32_t TextureCache::ComputeTextureStorage(const TextureInfo& src) { } void TextureCache::WritebackTexture(Texture* texture) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; VkFence fence = wb_command_pool_->BeginBatch(); auto alloc = wb_staging_buffer_.Acquire(texture->alloc_info.size, fence); @@ -1313,7 +1327,7 @@ void TextureCache::WritebackTexture(Texture* texture) { VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr, }; - vkBeginCommandBuffer(command_buffer, &begin_info); + dfn.vkBeginCommandBuffer(command_buffer, &begin_info); // TODO: Transition the texture to a transfer source. // TODO: copy depth/layers? @@ -1333,13 +1347,13 @@ void TextureCache::WritebackTexture(Texture* texture) { region.imageExtent.height = texture->texture_info.height + 1; region.imageExtent.depth = 1; - vkCmdCopyImageToBuffer(command_buffer, texture->image, - VK_IMAGE_LAYOUT_GENERAL, - wb_staging_buffer_.gpu_buffer(), 1, ®ion); + dfn.vkCmdCopyImageToBuffer(command_buffer, texture->image, + VK_IMAGE_LAYOUT_GENERAL, + wb_staging_buffer_.gpu_buffer(), 1, ®ion); // TODO: Transition the texture back to a shader resource. - vkEndCommandBuffer(command_buffer); + dfn.vkEndCommandBuffer(command_buffer); // Submit the command buffer. // Submit commands and wait. @@ -1356,11 +1370,12 @@ void TextureCache::WritebackTexture(Texture* texture) { 0, nullptr, }; - status = vkQueueSubmit(device_->primary_queue(), 1, &submit_info, fence); + status = + dfn.vkQueueSubmit(device_->primary_queue(), 1, &submit_info, fence); CheckResult(status, "vkQueueSubmit"); if (status == VK_SUCCESS) { - status = vkQueueWaitIdle(device_->primary_queue()); + status = dfn.vkQueueWaitIdle(device_->primary_queue()); CheckResult(status, "vkQueueWaitIdle"); } } @@ -1453,8 +1468,9 @@ VkDescriptorSet TextureCache::PrepareTextureSet( // Update the descriptor set. if (update_set_info->image_write_count > 0) { - vkUpdateDescriptorSets(*device_, update_set_info->image_write_count, - update_set_info->image_writes, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkUpdateDescriptorSets(*device_, update_set_info->image_write_count, + update_set_info->image_writes, 0, nullptr); } texture_sets_[hash] = descriptor_set; @@ -1613,8 +1629,9 @@ void TextureCache::ClearCache() { textures_.clear(); COUNT_profile_set("gpu/texture_cache/textures", 0); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); for (auto it = samplers_.begin(); it != samplers_.end(); ++it) { - vkDestroySampler(*device_, it->second->sampler, nullptr); + dfn.vkDestroySampler(*device_, it->second->sampler, nullptr); delete it->second; } samplers_.clear(); diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 2809bec7e..41e91886c 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -237,6 +237,8 @@ void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { void VulkanCommandProcessor::CreateSwapImage(VkCommandBuffer setup_buffer, VkExtent2D extents) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + VkImageCreateInfo image_info; std::memset(&image_info, 0, sizeof(VkImageCreateInfo)); image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; @@ -255,16 +257,16 @@ void VulkanCommandProcessor::CreateSwapImage(VkCommandBuffer setup_buffer, image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkImage image_fb; - auto status = vkCreateImage(*device_, &image_info, nullptr, &image_fb); + auto status = dfn.vkCreateImage(*device_, &image_info, nullptr, &image_fb); CheckResult(status, "vkCreateImage"); // Bind memory to image. VkMemoryRequirements mem_requirements; - vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements); + dfn.vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements); fb_memory_ = device_->AllocateMemory(mem_requirements, 0); assert_not_null(fb_memory_); - status = vkBindImageMemory(*device_, image_fb, fb_memory_, 0); + status = dfn.vkBindImageMemory(*device_, image_fb, fb_memory_, 0); CheckResult(status, "vkBindImageMemory"); std::lock_guard lock(swap_state_.mutex); @@ -281,8 +283,8 @@ void VulkanCommandProcessor::CreateSwapImage(VkCommandBuffer setup_buffer, VK_COMPONENT_SWIZZLE_A}, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, }; - status = - vkCreateImageView(*device_, &view_create_info, nullptr, &fb_image_view_); + status = dfn.vkCreateImageView(*device_, &view_create_info, nullptr, + &fb_image_view_); CheckResult(status, "vkCreateImageView"); VkFramebufferCreateInfo framebuffer_create_info = { @@ -296,8 +298,8 @@ void VulkanCommandProcessor::CreateSwapImage(VkCommandBuffer setup_buffer, extents.height, 1, }; - status = vkCreateFramebuffer(*device_, &framebuffer_create_info, nullptr, - &fb_framebuffer_); + status = dfn.vkCreateFramebuffer(*device_, &framebuffer_create_info, nullptr, + &fb_framebuffer_); CheckResult(status, "vkCreateFramebuffer"); // Transition image to general layout. @@ -313,20 +315,22 @@ void VulkanCommandProcessor::CreateSwapImage(VkCommandBuffer setup_buffer, barrier.image = image_fb; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(setup_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, - nullptr, 0, nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(setup_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, + nullptr, 0, nullptr, 1, &barrier); } void VulkanCommandProcessor::DestroySwapImage() { - vkDestroyFramebuffer(*device_, fb_framebuffer_, nullptr); - vkDestroyImageView(*device_, fb_image_view_, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + + dfn.vkDestroyFramebuffer(*device_, fb_framebuffer_, nullptr); + dfn.vkDestroyImageView(*device_, fb_image_view_, nullptr); std::lock_guard lock(swap_state_.mutex); - vkDestroyImage(*device_, - reinterpret_cast(swap_state_.front_buffer_texture), - nullptr); - vkFreeMemory(*device_, fb_memory_, nullptr); + dfn.vkDestroyImage( + *device_, reinterpret_cast(swap_state_.front_buffer_texture), + nullptr); + dfn.vkFreeMemory(*device_, fb_memory_, nullptr); swap_state_.front_buffer_texture = 0; fb_memory_ = nullptr; @@ -345,17 +349,19 @@ void VulkanCommandProcessor::BeginFrame() { current_command_buffer_ = command_buffer_pool_->AcquireEntry(); current_setup_buffer_ = command_buffer_pool_->AcquireEntry(); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + VkCommandBufferBeginInfo command_buffer_begin_info; command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; command_buffer_begin_info.pNext = nullptr; command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; command_buffer_begin_info.pInheritanceInfo = nullptr; - auto status = - vkBeginCommandBuffer(current_command_buffer_, &command_buffer_begin_info); + auto status = dfn.vkBeginCommandBuffer(current_command_buffer_, + &command_buffer_begin_info); CheckResult(status, "vkBeginCommandBuffer"); - status = - vkBeginCommandBuffer(current_setup_buffer_, &command_buffer_begin_info); + status = dfn.vkBeginCommandBuffer(current_setup_buffer_, + &command_buffer_begin_info); CheckResult(status, "vkBeginCommandBuffer"); // Flag renderdoc down to start a capture if requested. @@ -385,10 +391,11 @@ void VulkanCommandProcessor::EndFrame() { current_render_state_ = nullptr; } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; - status = vkEndCommandBuffer(current_setup_buffer_); + status = dfn.vkEndCommandBuffer(current_setup_buffer_); CheckResult(status, "vkEndCommandBuffer"); - status = vkEndCommandBuffer(current_command_buffer_); + status = dfn.vkEndCommandBuffer(current_command_buffer_); CheckResult(status, "vkEndCommandBuffer"); current_command_buffer_ = nullptr; @@ -403,6 +410,8 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_height) { SCOPE_profile_cpu_f("gpu"); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Build a final command buffer that copies the game's frontbuffer texture // into our backbuffer texture. VkCommandBuffer copy_commands = nullptr; @@ -420,7 +429,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, std::memset(&begin_info, 0, sizeof(begin_info)); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - auto status = vkBeginCommandBuffer(copy_commands, &begin_info); + auto status = dfn.vkBeginCommandBuffer(copy_commands, &begin_info); CheckResult(status, "vkBeginCommandBuffer"); if (!frontbuffer_ptr) { @@ -463,20 +472,20 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, barrier.image = texture->image; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(copy_commands, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, - 0, nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(copy_commands, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, + nullptr, 0, nullptr, 1, &barrier); barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; barrier.image = swap_fb; - vkCmdPipelineBarrier(copy_commands, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, - nullptr, 0, nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(copy_commands, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, + 0, nullptr, 0, nullptr, 1, &barrier); // Part of the source image that we want to blit from. VkRect2D src_rect = { @@ -502,7 +511,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, std::swap(barrier.oldLayout, barrier.newLayout); barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - vkCmdPipelineBarrier( + dfn.vkCmdPipelineBarrier( copy_commands, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); @@ -511,7 +520,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, swap_state_.height = frontbuffer_height; } - status = vkEndCommandBuffer(copy_commands); + status = dfn.vkEndCommandBuffer(copy_commands); CheckResult(status, "vkEndCommandBuffer"); // Queue up current command buffers. @@ -549,7 +558,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = nullptr; - status = vkQueueSubmit(queue_, 1, &submit_info, current_batch_fence_); + status = dfn.vkQueueSubmit(queue_, 1, &submit_info, current_batch_fence_); if (device_->is_renderdoc_attached() && capturing_) { device_->EndRenderDocFrameCapture(); capturing_ = false; @@ -559,7 +568,7 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, } } - vkWaitForFences(*device_, 1, ¤t_batch_fence_, VK_TRUE, -1); + dfn.vkWaitForFences(*device_, 1, ¤t_batch_fence_, VK_TRUE, -1); if (cache_clear_requested_) { cache_clear_requested_ = false; @@ -664,6 +673,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, } } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Configure the pipeline for drawing. // This encodes all render state (blend, depth, etc), our shader stages, // and our vertex input layout. @@ -675,8 +686,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, return false; } else if (pipeline_status == PipelineCache::UpdateStatus::kMismatch || full_update) { - vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline); + dfn.vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline); } pipeline_cache_->SetDynamicState(command_buffer, full_update); @@ -710,8 +721,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint32_t first_vertex = register_file_->values[XE_GPU_REG_VGT_INDX_OFFSET].u32; uint32_t first_instance = 0; - vkCmdDraw(command_buffer, index_count, instance_count, first_vertex, - first_instance); + dfn.vkCmdDraw(command_buffer, index_count, instance_count, first_vertex, + first_instance); } else { // Index buffer draw. uint32_t instance_count = 1; @@ -719,8 +730,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint32_t vertex_offset = register_file_->values[XE_GPU_REG_VGT_INDX_OFFSET].u32; uint32_t first_instance = 0; - vkCmdDrawIndexed(command_buffer, index_count, instance_count, first_index, - vertex_offset, first_instance); + dfn.vkCmdDrawIndexed(command_buffer, index_count, instance_count, + first_index, vertex_offset, first_instance); } return true; @@ -754,7 +765,8 @@ bool VulkanCommandProcessor::PopulateConstants(VkCommandBuffer command_buffer, uint32_t set_constant_offsets[2] = { static_cast(constant_offsets.first), static_cast(constant_offsets.second)}; - vkCmdBindDescriptorSets( + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdBindDescriptorSets( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &constant_descriptor_set, static_cast(xe::countof(set_constant_offsets)), @@ -806,8 +818,9 @@ bool VulkanCommandProcessor::PopulateIndexBuffer( VkIndexType index_type = info.format == xenos::IndexFormat::kInt32 ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_UINT16; - vkCmdBindIndexBuffer(command_buffer, buffer_ref.first, buffer_ref.second, - index_type); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdBindIndexBuffer(command_buffer, buffer_ref.first, buffer_ref.second, + index_type); return true; } @@ -835,9 +848,10 @@ bool VulkanCommandProcessor::PopulateVertexBuffers( return false; } - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline_cache_->pipeline_layout(), 2, 1, - &descriptor_set, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline_cache_->pipeline_layout(), 2, 1, + &descriptor_set, 0, nullptr); return true; } @@ -859,9 +873,10 @@ bool VulkanCommandProcessor::PopulateSamplers(VkCommandBuffer command_buffer, return false; } - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline_cache_->pipeline_layout(), 1, 1, - &descriptor_set, 0, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline_cache_->pipeline_layout(), 1, 1, + &descriptor_set, 0, nullptr); return true; } @@ -1066,6 +1081,7 @@ bool VulkanCommandProcessor::IssueCopy() { render_cache_->EndRenderPass(); current_render_state_ = nullptr; } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); auto command_buffer = current_command_buffer_; if (texture->image_layout == VK_IMAGE_LAYOUT_UNDEFINED) { @@ -1087,9 +1103,9 @@ bool VulkanCommandProcessor::IssueCopy() { : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; texture->image_layout = VK_IMAGE_LAYOUT_GENERAL; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, + nullptr, 0, nullptr, 1, &image_barrier); } // Transition the image into a transfer destination layout, if needed. @@ -1113,9 +1129,9 @@ bool VulkanCommandProcessor::IssueCopy() { is_color_source ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0, + nullptr, 1, &image_barrier); // Ask the render cache to copy to the resolve texture. auto edram_base = is_color_source ? color_edram_base : depth_edram_base; @@ -1176,10 +1192,11 @@ bool VulkanCommandProcessor::IssueCopy() { is_color_source ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &tile_image_barrier); + dfn.vkCmdPipelineBarrier( + command_buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &tile_image_barrier); auto render_pass = blitter_->GetRenderPass(texture->format, is_color_source); @@ -1200,8 +1217,8 @@ bool VulkanCommandProcessor::IssueCopy() { 1, }; - VkResult res = vkCreateFramebuffer(*device_, &fb_create_info, nullptr, - &texture->framebuffer); + VkResult res = dfn.vkCreateFramebuffer(*device_, &fb_create_info, + nullptr, &texture->framebuffer); CheckResult(res, "vkCreateFramebuffer"); } @@ -1268,11 +1285,11 @@ bool VulkanCommandProcessor::IssueCopy() { std::swap(tile_image_barrier.srcAccessMask, tile_image_barrier.dstAccessMask); std::swap(tile_image_barrier.oldLayout, tile_image_barrier.newLayout); - vkCmdPipelineBarrier(command_buffer, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, nullptr, 0, - nullptr, 1, &tile_image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, + nullptr, 0, nullptr, 1, &tile_image_barrier); } break; case CopyCommand::kConstantOne: @@ -1286,14 +1303,14 @@ bool VulkanCommandProcessor::IssueCopy() { image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT; std::swap(image_barrier.newLayout, image_barrier.oldLayout); - vkCmdPipelineBarrier(command_buffer, - is_color_source - ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - : VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, + is_color_source + ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + : VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &image_barrier); // Perform any requested clears. uint32_t copy_depth_clear = regs[XE_GPU_REG_RB_DEPTH_CLEAR].u32; diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index 0980e7f9e..fdb5b485a 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -59,8 +59,9 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, device_->queue_family_index(), }; + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); auto status = - vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_); + dfn.vkCreateCommandPool(*device_, &create_info, nullptr, &command_pool_); CheckResult(status, "vkCreateCommandPool"); return X_STATUS_SUCCESS; @@ -69,7 +70,11 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor, void VulkanGraphicsSystem::Shutdown() { GraphicsSystem::Shutdown(); - vkDestroyCommandPool(*device_, command_pool_, nullptr); + if (command_pool_ != VK_NULL_HANDLE) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkDestroyCommandPool(*device_, command_pool_, nullptr); + command_pool_ = VK_NULL_HANDLE; + } } std::unique_ptr VulkanGraphicsSystem::Capture() { @@ -79,6 +84,7 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { return nullptr; } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; VkCommandBufferAllocateInfo alloc_info = { @@ -90,7 +96,7 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { }; VkCommandBuffer cmd = nullptr; - status = vkAllocateCommandBuffers(*device_, &alloc_info, &cmd); + status = dfn.vkAllocateCommandBuffers(*device_, &alloc_info, &cmd); CheckResult(status, "vkAllocateCommandBuffers"); if (status != VK_SUCCESS) { return nullptr; @@ -102,14 +108,14 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr, }; - vkBeginCommandBuffer(cmd, &begin_info); + dfn.vkBeginCommandBuffer(cmd, &begin_info); auto front_buffer = reinterpret_cast(swap_state.front_buffer_texture); status = CreateCaptureBuffer(cmd, {swap_state.width, swap_state.height}); if (status != VK_SUCCESS) { - vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd); + dfn.vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd); return nullptr; } @@ -124,9 +130,9 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = front_buffer; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); // Copy front buffer into capture image. VkBufferImageCopy region = { @@ -135,8 +141,8 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { {0, 0, 0}, {swap_state.width, swap_state.height, 1}, }; - vkCmdCopyImageToBuffer(cmd, front_buffer, VK_IMAGE_LAYOUT_GENERAL, - capture_buffer_, 1, ®ion); + dfn.vkCmdCopyImageToBuffer(cmd, front_buffer, VK_IMAGE_LAYOUT_GENERAL, + capture_buffer_, 1, ®ion); VkBufferMemoryBarrier memory_barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER, @@ -149,11 +155,11 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { 0, VK_WHOLE_SIZE, }; - vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, - &memory_barrier, 0, nullptr); + dfn.vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, + 1, &memory_barrier, 0, nullptr); - status = vkEndCommandBuffer(cmd); + status = dfn.vkEndCommandBuffer(cmd); // Submit commands and wait. if (status == VK_SUCCESS) { @@ -169,21 +175,22 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { 0, nullptr, }; - status = vkQueueSubmit(device_->primary_queue(), 1, &submit_info, nullptr); + status = + dfn.vkQueueSubmit(device_->primary_queue(), 1, &submit_info, nullptr); CheckResult(status, "vkQueueSubmit"); if (status == VK_SUCCESS) { - status = vkQueueWaitIdle(device_->primary_queue()); + status = dfn.vkQueueWaitIdle(device_->primary_queue()); CheckResult(status, "vkQueueWaitIdle"); } } - vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd); + dfn.vkFreeCommandBuffers(*device_, command_pool_, 1, &cmd); void* data; if (status == VK_SUCCESS) { - status = vkMapMemory(*device_, capture_buffer_memory_, 0, VK_WHOLE_SIZE, 0, - &data); + status = dfn.vkMapMemory(*device_, capture_buffer_memory_, 0, VK_WHOLE_SIZE, + 0, &data); CheckResult(status, "vkMapMemory"); } @@ -197,7 +204,7 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { std::memcpy(raw_image->data.data(), data, raw_image->stride * raw_image->height); - vkUnmapMemory(*device_, capture_buffer_memory_); + dfn.vkUnmapMemory(*device_, capture_buffer_memory_); DestroyCaptureBuffer(); return raw_image; } @@ -208,6 +215,7 @@ std::unique_ptr VulkanGraphicsSystem::Capture() { VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd, VkExtent2D extents) { + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; VkBufferCreateInfo buffer_info = { @@ -220,7 +228,8 @@ VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd, 0, nullptr, }; - status = vkCreateBuffer(*device_, &buffer_info, nullptr, &capture_buffer_); + status = + dfn.vkCreateBuffer(*device_, &buffer_info, nullptr, &capture_buffer_); if (status != VK_SUCCESS) { return status; } @@ -229,17 +238,18 @@ VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd, // Bind memory to buffer. VkMemoryRequirements mem_requirements; - vkGetBufferMemoryRequirements(*device_, capture_buffer_, &mem_requirements); + dfn.vkGetBufferMemoryRequirements(*device_, capture_buffer_, + &mem_requirements); capture_buffer_memory_ = device_->AllocateMemory( mem_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); assert_not_null(capture_buffer_memory_); - status = - vkBindBufferMemory(*device_, capture_buffer_, capture_buffer_memory_, 0); + status = dfn.vkBindBufferMemory(*device_, capture_buffer_, + capture_buffer_memory_, 0); CheckResult(status, "vkBindImageMemory"); if (status != VK_SUCCESS) { - vkDestroyBuffer(*device_, capture_buffer_, nullptr); + dfn.vkDestroyBuffer(*device_, capture_buffer_, nullptr); return status; } @@ -247,8 +257,9 @@ VkResult VulkanGraphicsSystem::CreateCaptureBuffer(VkCommandBuffer cmd, } void VulkanGraphicsSystem::DestroyCaptureBuffer() { - vkDestroyBuffer(*device_, capture_buffer_, nullptr); - vkFreeMemory(*device_, capture_buffer_memory_, nullptr); + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkDestroyBuffer(*device_, capture_buffer_, nullptr); + dfn.vkFreeMemory(*device_, capture_buffer_memory_, nullptr); capture_buffer_ = nullptr; capture_buffer_memory_ = nullptr; capture_buffer_size_ = 0; @@ -286,6 +297,7 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) { return; } + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device_->dfn(); auto swap_chain = display_context_->swap_chain(); auto copy_cmd_buffer = swap_chain->copy_cmd_buffer(); auto front_buffer = @@ -302,9 +314,9 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) { barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = front_buffer; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(copy_cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, - nullptr, 1, &barrier); + dfn.vkCmdPipelineBarrier(copy_cmd_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); VkImageBlit region; region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; @@ -317,10 +329,10 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) { region.dstOffsets[1] = {static_cast(swap_chain->surface_width()), static_cast(swap_chain->surface_height()), 1}; - vkCmdBlitImage(copy_cmd_buffer, front_buffer, VK_IMAGE_LAYOUT_GENERAL, - swap_chain->surface_image(), - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, - VK_FILTER_LINEAR); + dfn.vkCmdBlitImage(copy_cmd_buffer, front_buffer, VK_IMAGE_LAYOUT_GENERAL, + swap_chain->surface_image(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, + VK_FILTER_LINEAR); } } // namespace vulkan diff --git a/src/xenia/gpu/vulkan/vulkan_shader.cc b/src/xenia/gpu/vulkan/vulkan_shader.cc index 99333f062..f70be58eb 100644 --- a/src/xenia/gpu/vulkan/vulkan_shader.cc +++ b/src/xenia/gpu/vulkan/vulkan_shader.cc @@ -30,7 +30,9 @@ VulkanShader::VulkanShader(ui::vulkan::VulkanDevice* device, VulkanShader::VulkanTranslation::~VulkanTranslation() { if (shader_module_) { const VulkanShader& vulkan_shader = static_cast(shader()); - vkDestroyShaderModule(*vulkan_shader.device_, shader_module_, nullptr); + const ui::vulkan::VulkanDevice* device = vulkan_shader.device_; + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device->dfn(); + dfn.vkDestroyShaderModule(*device, shader_module_, nullptr); shader_module_ = nullptr; } } @@ -40,7 +42,8 @@ bool VulkanShader::VulkanTranslation::Prepare() { assert_true(is_valid()); const VulkanShader& vulkan_shader = static_cast(shader()); - ui::vulkan::VulkanDevice* device = vulkan_shader.device_; + const ui::vulkan::VulkanDevice* device = vulkan_shader.device_; + const ui::vulkan::VulkanDevice::DeviceFunctions& dfn = device->dfn(); // Create the shader module. VkShaderModuleCreateInfo shader_info; @@ -51,7 +54,7 @@ bool VulkanShader::VulkanTranslation::Prepare() { shader_info.pCode = reinterpret_cast(translated_binary().data()); auto status = - vkCreateShaderModule(*device, &shader_info, nullptr, &shader_module_); + dfn.vkCreateShaderModule(*device, &shader_info, nullptr, &shader_module_); CheckResult(status, "vkCreateShaderModule"); char type_char; diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index 348e12371..ce89331c9 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -22,7 +22,6 @@ project("xenia-hid-demo") links({ "fmt", "imgui", - "volk", "xenia-base", "xenia-helper-sdl", "xenia-hid", diff --git a/src/xenia/ui/vulkan/blitter.cc b/src/xenia/ui/vulkan/blitter.cc index e4394eef7..165301d3c 100644 --- a/src/xenia/ui/vulkan/blitter.cc +++ b/src/xenia/ui/vulkan/blitter.cc @@ -26,6 +26,7 @@ Blitter::~Blitter() { Shutdown(); } VkResult Blitter::Initialize(VulkanDevice* device) { device_ = device; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Shaders @@ -34,8 +35,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { shader_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shader_create_info.codeSize = sizeof(blit_vert); shader_create_info.pCode = reinterpret_cast(blit_vert); - status = vkCreateShaderModule(*device_, &shader_create_info, nullptr, - &blit_vertex_); + status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr, + &blit_vertex_); CheckResult(status, "vkCreateShaderModule"); if (status != VK_SUCCESS) { return status; @@ -46,8 +47,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { shader_create_info.codeSize = sizeof(blit_color_frag); shader_create_info.pCode = reinterpret_cast(blit_color_frag); - status = vkCreateShaderModule(*device_, &shader_create_info, nullptr, - &blit_color_); + status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr, + &blit_color_); CheckResult(status, "vkCreateShaderModule"); if (status != VK_SUCCESS) { return status; @@ -58,8 +59,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { shader_create_info.codeSize = sizeof(blit_depth_frag); shader_create_info.pCode = reinterpret_cast(blit_depth_frag); - status = vkCreateShaderModule(*device_, &shader_create_info, nullptr, - &blit_depth_); + status = dfn.vkCreateShaderModule(*device_, &shader_create_info, nullptr, + &blit_depth_); CheckResult(status, "vkCreateShaderModule"); if (status != VK_SUCCESS) { return status; @@ -83,8 +84,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { texture_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; texture_binding.pImmutableSamplers = nullptr; texture_set_layout_info.pBindings = &texture_binding; - status = vkCreateDescriptorSetLayout(*device_, &texture_set_layout_info, - nullptr, &descriptor_set_layout_); + status = dfn.vkCreateDescriptorSetLayout(*device_, &texture_set_layout_info, + nullptr, &descriptor_set_layout_); CheckResult(status, "vkCreateDescriptorSetLayout"); if (status != VK_SUCCESS) { return status; @@ -119,8 +120,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { pipeline_layout_info.pushConstantRangeCount = static_cast(xe::countof(push_constant_ranges)); pipeline_layout_info.pPushConstantRanges = push_constant_ranges; - status = vkCreatePipelineLayout(*device_, &pipeline_layout_info, nullptr, - &pipeline_layout_); + status = dfn.vkCreatePipelineLayout(*device_, &pipeline_layout_info, nullptr, + &pipeline_layout_); CheckResult(status, "vkCreatePipelineLayout"); if (status != VK_SUCCESS) { return status; @@ -147,8 +148,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { VK_BORDER_COLOR_INT_TRANSPARENT_BLACK, VK_FALSE, }; - status = - vkCreateSampler(*device_, &sampler_create_info, nullptr, &samp_nearest_); + status = dfn.vkCreateSampler(*device_, &sampler_create_info, nullptr, + &samp_nearest_); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -157,8 +158,8 @@ VkResult Blitter::Initialize(VulkanDevice* device) { sampler_create_info.minFilter = VK_FILTER_LINEAR; sampler_create_info.magFilter = VK_FILTER_LINEAR; sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - status = - vkCreateSampler(*device_, &sampler_create_info, nullptr, &samp_linear_); + status = dfn.vkCreateSampler(*device_, &sampler_create_info, nullptr, + &samp_linear_); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -168,49 +169,50 @@ VkResult Blitter::Initialize(VulkanDevice* device) { } void Blitter::Shutdown() { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); if (samp_nearest_) { - vkDestroySampler(*device_, samp_nearest_, nullptr); + dfn.vkDestroySampler(*device_, samp_nearest_, nullptr); samp_nearest_ = nullptr; } if (samp_linear_) { - vkDestroySampler(*device_, samp_linear_, nullptr); + dfn.vkDestroySampler(*device_, samp_linear_, nullptr); samp_linear_ = nullptr; } if (blit_vertex_) { - vkDestroyShaderModule(*device_, blit_vertex_, nullptr); + dfn.vkDestroyShaderModule(*device_, blit_vertex_, nullptr); blit_vertex_ = nullptr; } if (blit_color_) { - vkDestroyShaderModule(*device_, blit_color_, nullptr); + dfn.vkDestroyShaderModule(*device_, blit_color_, nullptr); blit_color_ = nullptr; } if (blit_depth_) { - vkDestroyShaderModule(*device_, blit_depth_, nullptr); + dfn.vkDestroyShaderModule(*device_, blit_depth_, nullptr); blit_depth_ = nullptr; } if (pipeline_color_) { - vkDestroyPipeline(*device_, pipeline_color_, nullptr); + dfn.vkDestroyPipeline(*device_, pipeline_color_, nullptr); pipeline_color_ = nullptr; } if (pipeline_depth_) { - vkDestroyPipeline(*device_, pipeline_depth_, nullptr); + dfn.vkDestroyPipeline(*device_, pipeline_depth_, nullptr); pipeline_depth_ = nullptr; } if (pipeline_layout_) { - vkDestroyPipelineLayout(*device_, pipeline_layout_, nullptr); + dfn.vkDestroyPipelineLayout(*device_, pipeline_layout_, nullptr); pipeline_layout_ = nullptr; } if (descriptor_set_layout_) { - vkDestroyDescriptorSetLayout(*device_, descriptor_set_layout_, nullptr); + dfn.vkDestroyDescriptorSetLayout(*device_, descriptor_set_layout_, nullptr); descriptor_set_layout_ = nullptr; } for (auto& pipeline : pipelines_) { - vkDestroyPipeline(*device_, pipeline.second, nullptr); + dfn.vkDestroyPipeline(*device_, pipeline.second, nullptr); } pipelines_.clear(); for (auto& pass : render_passes_) { - vkDestroyRenderPass(*device_, pass.second, nullptr); + dfn.vkDestroyRenderPass(*device_, pass.second, nullptr); } render_passes_.clear(); } @@ -230,6 +232,8 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, VkFramebuffer dst_framebuffer, VkViewport viewport, VkRect2D scissor, VkFilter filter, bool color_or_depth, bool swap_channels) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Do we need a full draw, or can we cheap out with a blit command? bool full_draw = swap_channels || true; if (full_draw) { @@ -249,18 +253,18 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, nullptr, }; - vkCmdBeginRenderPass(command_buffer, &render_pass_info, - VK_SUBPASS_CONTENTS_INLINE); + dfn.vkCmdBeginRenderPass(command_buffer, &render_pass_info, + VK_SUBPASS_CONTENTS_INLINE); - vkCmdSetViewport(command_buffer, 0, 1, &viewport); - vkCmdSetScissor(command_buffer, 0, 1, &scissor); + dfn.vkCmdSetViewport(command_buffer, 0, 1, &viewport); + dfn.vkCmdSetScissor(command_buffer, 0, 1, &scissor); // Acquire a pipeline. auto pipeline = GetPipeline(render_pass, color_or_depth ? blit_color_ : blit_depth_, color_or_depth); - vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline); + dfn.vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline); // Acquire and update a descriptor set for this image. auto set = descriptor_pool_->AcquireEntry(descriptor_set_layout_); @@ -287,10 +291,10 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, write.pImageInfo = ℑ write.pBufferInfo = nullptr; write.pTexelBufferView = nullptr; - vkUpdateDescriptorSets(*device_, 1, &write, 0, nullptr); + dfn.vkUpdateDescriptorSets(*device_, 1, &write, 0, nullptr); - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline_layout_, 0, 1, &set, 0, nullptr); + dfn.vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline_layout_, 0, 1, &set, 0, nullptr); VtxPushConstants vtx_constants = { { @@ -306,9 +310,9 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, float(dst_rect.extent.height) / dst_extents.height, }, }; - vkCmdPushConstants(command_buffer, pipeline_layout_, - VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(VtxPushConstants), - &vtx_constants); + dfn.vkCmdPushConstants(command_buffer, pipeline_layout_, + VK_SHADER_STAGE_VERTEX_BIT, 0, + sizeof(VtxPushConstants), &vtx_constants); PixPushConstants pix_constants = { 0, @@ -316,12 +320,12 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, 0, swap_channels ? 1 : 0, }; - vkCmdPushConstants(command_buffer, pipeline_layout_, - VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(VtxPushConstants), - sizeof(PixPushConstants), &pix_constants); + dfn.vkCmdPushConstants( + command_buffer, pipeline_layout_, VK_SHADER_STAGE_FRAGMENT_BIT, + sizeof(VtxPushConstants), sizeof(PixPushConstants), &pix_constants); - vkCmdDraw(command_buffer, 4, 1, 0, 0); - vkCmdEndRenderPass(command_buffer); + dfn.vkCmdDraw(command_buffer, 4, 1, 0, 0); + dfn.vkCmdEndRenderPass(command_buffer); } } @@ -421,8 +425,9 @@ VkRenderPass Blitter::CreateRenderPass(VkFormat output_format, nullptr, }; VkRenderPass renderpass = nullptr; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult result = - vkCreateRenderPass(*device_, &renderpass_info, nullptr, &renderpass); + dfn.vkCreateRenderPass(*device_, &renderpass_info, nullptr, &renderpass); CheckResult(result, "vkCreateRenderPass"); return renderpass; @@ -431,6 +436,7 @@ VkRenderPass Blitter::CreateRenderPass(VkFormat output_format, VkPipeline Blitter::CreatePipeline(VkRenderPass render_pass, VkShaderModule frag_shader, bool color_or_depth) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult result = VK_SUCCESS; // Pipeline @@ -576,8 +582,8 @@ VkPipeline Blitter::CreatePipeline(VkRenderPass render_pass, pipeline_info.basePipelineIndex = -1; VkPipeline pipeline = nullptr; - result = vkCreateGraphicsPipelines(*device_, nullptr, 1, &pipeline_info, - nullptr, &pipeline); + result = dfn.vkCreateGraphicsPipelines(*device_, nullptr, 1, &pipeline_info, + nullptr, &pipeline); CheckResult(result, "vkCreateGraphicsPipelines"); return pipeline; diff --git a/src/xenia/ui/vulkan/circular_buffer.cc b/src/xenia/ui/vulkan/circular_buffer.cc index 06cb68aa7..543342efe 100644 --- a/src/xenia/ui/vulkan/circular_buffer.cc +++ b/src/xenia/ui/vulkan/circular_buffer.cc @@ -22,6 +22,7 @@ namespace vulkan { CircularBuffer::CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage, VkDeviceSize capacity, VkDeviceSize alignment) : device_(device), capacity_(capacity) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Create our internal buffer. @@ -34,14 +35,14 @@ CircularBuffer::CircularBuffer(VulkanDevice* device, VkBufferUsageFlags usage, buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buffer_info.queueFamilyIndexCount = 0; buffer_info.pQueueFamilyIndices = nullptr; - status = vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_); + status = dfn.vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_); CheckResult(status, "vkCreateBuffer"); if (status != VK_SUCCESS) { assert_always(); } VkMemoryRequirements reqs; - vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs); + dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs); alignment_ = xe::round_up(alignment, reqs.alignment); } CircularBuffer::~CircularBuffer() { Shutdown(); } @@ -52,10 +53,12 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory, gpu_memory_ = memory; gpu_base_ = offset; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; // Bind the buffer to its backing memory. - status = vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_); + status = + dfn.vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_); CheckResult(status, "vkBindBufferMemory"); if (status != VK_SUCCESS) { XELOGE("CircularBuffer::Initialize - Failed to bind memory!"); @@ -64,8 +67,8 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory, } // Map the memory so we can access it. - status = vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0, - reinterpret_cast(&host_base_)); + status = dfn.vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0, + reinterpret_cast(&host_base_)); CheckResult(status, "vkMapMemory"); if (status != VK_SUCCESS) { XELOGE("CircularBuffer::Initialize - Failed to map memory!"); @@ -77,10 +80,11 @@ VkResult CircularBuffer::Initialize(VkDeviceMemory memory, } VkResult CircularBuffer::Initialize() { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status = VK_SUCCESS; VkMemoryRequirements reqs; - vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs); + dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs); // Allocate memory from the device to back the buffer. owns_gpu_memory_ = true; @@ -95,7 +99,8 @@ VkResult CircularBuffer::Initialize() { gpu_base_ = 0; // Bind the buffer to its backing memory. - status = vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_); + status = + dfn.vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_); CheckResult(status, "vkBindBufferMemory"); if (status != VK_SUCCESS) { XELOGE("CircularBuffer::Initialize - Failed to bind memory!"); @@ -104,8 +109,8 @@ VkResult CircularBuffer::Initialize() { } // Map the memory so we can access it. - status = vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0, - reinterpret_cast(&host_base_)); + status = dfn.vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0, + reinterpret_cast(&host_base_)); CheckResult(status, "vkMapMemory"); if (status != VK_SUCCESS) { XELOGE("CircularBuffer::Initialize - Failed to map memory!"); @@ -118,22 +123,24 @@ VkResult CircularBuffer::Initialize() { void CircularBuffer::Shutdown() { Clear(); + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); if (host_base_) { - vkUnmapMemory(*device_, gpu_memory_); + dfn.vkUnmapMemory(*device_, gpu_memory_); host_base_ = nullptr; } if (gpu_buffer_) { - vkDestroyBuffer(*device_, gpu_buffer_, nullptr); + dfn.vkDestroyBuffer(*device_, gpu_buffer_, nullptr); gpu_buffer_ = nullptr; } if (gpu_memory_ && owns_gpu_memory_) { - vkFreeMemory(*device_, gpu_memory_, nullptr); + dfn.vkFreeMemory(*device_, gpu_memory_, nullptr); gpu_memory_ = nullptr; } } void CircularBuffer::GetBufferMemoryRequirements(VkMemoryRequirements* reqs) { - vkGetBufferMemoryRequirements(*device_, gpu_buffer_, reqs); + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + dfn.vkGetBufferMemoryRequirements(*device_, gpu_buffer_, reqs); } bool CircularBuffer::CanAcquire(VkDeviceSize length) { @@ -224,23 +231,25 @@ CircularBuffer::Allocation* CircularBuffer::Acquire(VkDeviceSize length, } void CircularBuffer::Flush(Allocation* allocation) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkMappedMemoryRange range; range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range.pNext = nullptr; range.memory = gpu_memory_; range.offset = gpu_base_ + allocation->offset; range.size = allocation->length; - vkFlushMappedMemoryRanges(*device_, 1, &range); + dfn.vkFlushMappedMemoryRanges(*device_, 1, &range); } void CircularBuffer::Flush(VkDeviceSize offset, VkDeviceSize length) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkMappedMemoryRange range; range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range.pNext = nullptr; range.memory = gpu_memory_; range.offset = gpu_base_ + offset; range.size = length; - vkFlushMappedMemoryRanges(*device_, 1, &range); + dfn.vkFlushMappedMemoryRanges(*device_, 1, &range); } void CircularBuffer::Clear() { @@ -249,12 +258,14 @@ void CircularBuffer::Clear() { } void CircularBuffer::Scavenge() { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Stash the last signalled fence VkFence fence = nullptr; while (!allocations_.empty()) { Allocation& alloc = allocations_.front(); if (fence != alloc.fence && - vkGetFenceStatus(*device_, alloc.fence) != VK_SUCCESS) { + dfn.vkGetFenceStatus(*device_, alloc.fence) != VK_SUCCESS) { // Don't bother freeing following allocations to ensure proper ordering. break; } diff --git a/src/xenia/ui/vulkan/fenced_pools.cc b/src/xenia/ui/vulkan/fenced_pools.cc index f7aeffff3..f6ce79662 100644 --- a/src/xenia/ui/vulkan/fenced_pools.cc +++ b/src/xenia/ui/vulkan/fenced_pools.cc @@ -19,9 +19,11 @@ namespace vulkan { using xe::ui::vulkan::CheckResult; -CommandBufferPool::CommandBufferPool(VkDevice device, +CommandBufferPool::CommandBufferPool(const VulkanDevice& device, uint32_t queue_family_index) : BaseFencedPool(device) { + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + // Create the pool used for allocating buffers. // They are marked as transient (short-lived) and cycled frequently. VkCommandPoolCreateInfo cmd_pool_info; @@ -31,7 +33,7 @@ CommandBufferPool::CommandBufferPool(VkDevice device, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; cmd_pool_info.queueFamilyIndex = queue_family_index; auto err = - vkCreateCommandPool(device_, &cmd_pool_info, nullptr, &command_pool_); + dfn.vkCreateCommandPool(device_, &cmd_pool_info, nullptr, &command_pool_); CheckResult(err, "vkCreateCommandPool"); // Allocate a bunch of command buffers to start. @@ -43,8 +45,8 @@ CommandBufferPool::CommandBufferPool(VkDevice device, command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; command_buffer_info.commandBufferCount = kDefaultCount; VkCommandBuffer command_buffers[kDefaultCount]; - err = - vkAllocateCommandBuffers(device_, &command_buffer_info, command_buffers); + err = dfn.vkAllocateCommandBuffers(device_, &command_buffer_info, + command_buffers); CheckResult(err, "vkCreateCommandBuffer"); for (size_t i = 0; i < xe::countof(command_buffers); ++i) { PushEntry(command_buffers[i], nullptr); @@ -53,7 +55,8 @@ CommandBufferPool::CommandBufferPool(VkDevice device, CommandBufferPool::~CommandBufferPool() { FreeAllEntries(); - vkDestroyCommandPool(device_, command_pool_, nullptr); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkDestroyCommandPool(device_, command_pool_, nullptr); command_pool_ = nullptr; } @@ -67,17 +70,19 @@ VkCommandBuffer CommandBufferPool::AllocateEntry(void* data) { VkCommandBufferLevel(reinterpret_cast(data)); command_buffer_info.commandBufferCount = 1; VkCommandBuffer command_buffer; - auto err = - vkAllocateCommandBuffers(device_, &command_buffer_info, &command_buffer); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + auto err = dfn.vkAllocateCommandBuffers(device_, &command_buffer_info, + &command_buffer); CheckResult(err, "vkCreateCommandBuffer"); return command_buffer; } void CommandBufferPool::FreeEntry(VkCommandBuffer handle) { - vkFreeCommandBuffers(device_, command_pool_, 1, &handle); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkFreeCommandBuffers(device_, command_pool_, 1, &handle); } -DescriptorPool::DescriptorPool(VkDevice device, uint32_t max_count, +DescriptorPool::DescriptorPool(const VulkanDevice& device, uint32_t max_count, std::vector pool_sizes) : BaseFencedPool(device) { VkDescriptorPoolCreateInfo descriptor_pool_info; @@ -88,13 +93,15 @@ DescriptorPool::DescriptorPool(VkDevice device, uint32_t max_count, descriptor_pool_info.maxSets = max_count; descriptor_pool_info.poolSizeCount = uint32_t(pool_sizes.size()); descriptor_pool_info.pPoolSizes = pool_sizes.data(); - auto err = vkCreateDescriptorPool(device, &descriptor_pool_info, nullptr, - &descriptor_pool_); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + auto err = dfn.vkCreateDescriptorPool(device, &descriptor_pool_info, nullptr, + &descriptor_pool_); CheckResult(err, "vkCreateDescriptorPool"); } DescriptorPool::~DescriptorPool() { FreeAllEntries(); - vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr); descriptor_pool_ = nullptr; } @@ -108,15 +115,17 @@ VkDescriptorSet DescriptorPool::AllocateEntry(void* data) { set_alloc_info.descriptorPool = descriptor_pool_; set_alloc_info.descriptorSetCount = 1; set_alloc_info.pSetLayouts = &layout; + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); auto err = - vkAllocateDescriptorSets(device_, &set_alloc_info, &descriptor_set); + dfn.vkAllocateDescriptorSets(device_, &set_alloc_info, &descriptor_set); CheckResult(err, "vkAllocateDescriptorSets"); return descriptor_set; } void DescriptorPool::FreeEntry(VkDescriptorSet handle) { - vkFreeDescriptorSets(device_, descriptor_pool_, 1, &handle); + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + dfn.vkFreeDescriptorSets(device_, descriptor_pool_, 1, &handle); } } // namespace vulkan diff --git a/src/xenia/ui/vulkan/fenced_pools.h b/src/xenia/ui/vulkan/fenced_pools.h index d64bcd6ac..0af6dd694 100644 --- a/src/xenia/ui/vulkan/fenced_pools.h +++ b/src/xenia/ui/vulkan/fenced_pools.h @@ -14,6 +14,7 @@ #include "xenia/base/assert.h" #include "xenia/ui/vulkan/vulkan.h" +#include "xenia/ui/vulkan/vulkan_device.h" #include "xenia/ui/vulkan/vulkan_util.h" namespace xe { @@ -28,7 +29,7 @@ namespace vulkan { template class BaseFencedPool { public: - BaseFencedPool(VkDevice device) : device_(device) {} + BaseFencedPool(const VulkanDevice& device) : device_(device) {} virtual ~BaseFencedPool() { // TODO(benvanik): wait on fence until done. @@ -47,11 +48,12 @@ class BaseFencedPool { // Checks all pending batches for completion and scavenges their entries. // This should be called as frequently as reasonable. void Scavenge() { + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); while (pending_batch_list_head_) { auto batch = pending_batch_list_head_; assert_not_null(batch->fence); - VkResult status = vkGetFenceStatus(device_, batch->fence); + VkResult status = dfn.vkGetFenceStatus(device_, batch->fence); if (status == VK_SUCCESS || status == VK_ERROR_DEVICE_LOST) { // Batch has completed. Reclaim. pending_batch_list_head_ = batch->next; @@ -80,6 +82,7 @@ class BaseFencedPool { VkFence BeginBatch(VkFence fence = nullptr) { assert_null(open_batch_); Batch* batch = nullptr; + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); if (free_batch_list_head_) { // Reuse a batch. batch = free_batch_list_head_; @@ -88,10 +91,10 @@ class BaseFencedPool { if (batch->flags & kBatchOwnsFence && !fence) { // Reset owned fence. - vkResetFences(device_, 1, &batch->fence); + dfn.vkResetFences(device_, 1, &batch->fence); } else if ((batch->flags & kBatchOwnsFence) && fence) { // Transfer owned -> external - vkDestroyFence(device_, batch->fence, nullptr); + dfn.vkDestroyFence(device_, batch->fence, nullptr); batch->fence = fence; batch->flags &= ~kBatchOwnsFence; } else if (!(batch->flags & kBatchOwnsFence) && !fence) { @@ -100,7 +103,8 @@ class BaseFencedPool { info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; info.pNext = nullptr; info.flags = 0; - VkResult res = vkCreateFence(device_, &info, nullptr, &batch->fence); + VkResult res = + dfn.vkCreateFence(device_, &info, nullptr, &batch->fence); if (res != VK_SUCCESS) { assert_always(); } @@ -121,7 +125,8 @@ class BaseFencedPool { info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; info.pNext = nullptr; info.flags = 0; - VkResult res = vkCreateFence(device_, &info, nullptr, &batch->fence); + VkResult res = + dfn.vkCreateFence(device_, &info, nullptr, &batch->fence); if (res != VK_SUCCESS) { assert_always(); } @@ -239,13 +244,14 @@ class BaseFencedPool { } void FreeAllEntries() { + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); // Run down free lists. while (free_batch_list_head_) { auto batch = free_batch_list_head_; free_batch_list_head_ = batch->next; if (batch->flags & kBatchOwnsFence) { - vkDestroyFence(device_, batch->fence, nullptr); + dfn.vkDestroyFence(device_, batch->fence, nullptr); batch->fence = nullptr; } delete batch; @@ -258,7 +264,7 @@ class BaseFencedPool { } } - VkDevice device_ = nullptr; + const VulkanDevice& device_; private: struct Entry { @@ -288,7 +294,7 @@ class CommandBufferPool public: typedef BaseFencedPool Base; - CommandBufferPool(VkDevice device, uint32_t queue_family_index); + CommandBufferPool(const VulkanDevice& device, uint32_t queue_family_index); ~CommandBufferPool() override; VkCommandBuffer AcquireEntry( @@ -308,7 +314,7 @@ class DescriptorPool : public BaseFencedPool { public: typedef BaseFencedPool Base; - DescriptorPool(VkDevice device, uint32_t max_count, + DescriptorPool(const VulkanDevice& device, uint32_t max_count, std::vector pool_sizes); ~DescriptorPool() override; diff --git a/src/xenia/ui/vulkan/functions/device_1_0.inc b/src/xenia/ui/vulkan/functions/device_1_0.inc new file mode 100644 index 000000000..aed4f435a --- /dev/null +++ b/src/xenia/ui/vulkan/functions/device_1_0.inc @@ -0,0 +1,83 @@ +// Vulkan 1.0 core device functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkAllocateCommandBuffers) +XE_UI_VULKAN_FUNCTION(vkAllocateDescriptorSets) +XE_UI_VULKAN_FUNCTION(vkAllocateMemory) +XE_UI_VULKAN_FUNCTION(vkBeginCommandBuffer) +XE_UI_VULKAN_FUNCTION(vkCmdBeginRenderPass) +XE_UI_VULKAN_FUNCTION(vkCmdBindDescriptorSets) +XE_UI_VULKAN_FUNCTION(vkBindBufferMemory) +XE_UI_VULKAN_FUNCTION(vkBindImageMemory) +XE_UI_VULKAN_FUNCTION(vkCmdBindIndexBuffer) +XE_UI_VULKAN_FUNCTION(vkCmdBindPipeline) +XE_UI_VULKAN_FUNCTION(vkCmdBindVertexBuffers) +XE_UI_VULKAN_FUNCTION(vkCmdBlitImage) +XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage) +XE_UI_VULKAN_FUNCTION(vkCmdClearDepthStencilImage) +XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage) +XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer) +XE_UI_VULKAN_FUNCTION(vkCmdDraw) +XE_UI_VULKAN_FUNCTION(vkCmdDrawIndexed) +XE_UI_VULKAN_FUNCTION(vkCmdEndRenderPass) +XE_UI_VULKAN_FUNCTION(vkCmdExecuteCommands) +XE_UI_VULKAN_FUNCTION(vkCmdFillBuffer) +XE_UI_VULKAN_FUNCTION(vkCmdPipelineBarrier) +XE_UI_VULKAN_FUNCTION(vkCmdPushConstants) +XE_UI_VULKAN_FUNCTION(vkCmdResolveImage) +XE_UI_VULKAN_FUNCTION(vkCmdSetBlendConstants) +XE_UI_VULKAN_FUNCTION(vkCmdSetDepthBias) +XE_UI_VULKAN_FUNCTION(vkCmdSetDepthBounds) +XE_UI_VULKAN_FUNCTION(vkCmdSetLineWidth) +XE_UI_VULKAN_FUNCTION(vkCmdSetScissor) +XE_UI_VULKAN_FUNCTION(vkCmdSetStencilCompareMask) +XE_UI_VULKAN_FUNCTION(vkCmdSetStencilReference) +XE_UI_VULKAN_FUNCTION(vkCmdSetStencilWriteMask) +XE_UI_VULKAN_FUNCTION(vkCmdSetViewport) +XE_UI_VULKAN_FUNCTION(vkCreateBuffer) +XE_UI_VULKAN_FUNCTION(vkCreateCommandPool) +XE_UI_VULKAN_FUNCTION(vkCreateDescriptorPool) +XE_UI_VULKAN_FUNCTION(vkCreateDescriptorSetLayout) +XE_UI_VULKAN_FUNCTION(vkCreateFence) +XE_UI_VULKAN_FUNCTION(vkCreateFramebuffer) +XE_UI_VULKAN_FUNCTION(vkCreateGraphicsPipelines) +XE_UI_VULKAN_FUNCTION(vkCreateImage) +XE_UI_VULKAN_FUNCTION(vkCreateImageView) +XE_UI_VULKAN_FUNCTION(vkCreatePipelineCache) +XE_UI_VULKAN_FUNCTION(vkCreatePipelineLayout) +XE_UI_VULKAN_FUNCTION(vkCreateRenderPass) +XE_UI_VULKAN_FUNCTION(vkCreateSampler) +XE_UI_VULKAN_FUNCTION(vkCreateSemaphore) +XE_UI_VULKAN_FUNCTION(vkCreateShaderModule) +XE_UI_VULKAN_FUNCTION(vkDestroyBuffer) +XE_UI_VULKAN_FUNCTION(vkDestroyCommandPool) +XE_UI_VULKAN_FUNCTION(vkDestroyDescriptorPool) +XE_UI_VULKAN_FUNCTION(vkDestroyDescriptorSetLayout) +XE_UI_VULKAN_FUNCTION(vkDestroyFence) +XE_UI_VULKAN_FUNCTION(vkDestroyFramebuffer) +XE_UI_VULKAN_FUNCTION(vkDestroyImage) +XE_UI_VULKAN_FUNCTION(vkDestroyImageView) +XE_UI_VULKAN_FUNCTION(vkDestroyPipeline) +XE_UI_VULKAN_FUNCTION(vkDestroyPipelineCache) +XE_UI_VULKAN_FUNCTION(vkDestroyPipelineLayout) +XE_UI_VULKAN_FUNCTION(vkDestroyRenderPass) +XE_UI_VULKAN_FUNCTION(vkDestroySampler) +XE_UI_VULKAN_FUNCTION(vkDestroySemaphore) +XE_UI_VULKAN_FUNCTION(vkDestroyShaderModule) +XE_UI_VULKAN_FUNCTION(vkEndCommandBuffer) +XE_UI_VULKAN_FUNCTION(vkFlushMappedMemoryRanges) +XE_UI_VULKAN_FUNCTION(vkFreeCommandBuffers) +XE_UI_VULKAN_FUNCTION(vkFreeDescriptorSets) +XE_UI_VULKAN_FUNCTION(vkFreeMemory) +XE_UI_VULKAN_FUNCTION(vkGetDeviceQueue) +XE_UI_VULKAN_FUNCTION(vkGetBufferMemoryRequirements) +XE_UI_VULKAN_FUNCTION(vkGetFenceStatus) +XE_UI_VULKAN_FUNCTION(vkGetImageMemoryRequirements) +XE_UI_VULKAN_FUNCTION(vkGetImageSubresourceLayout) +XE_UI_VULKAN_FUNCTION(vkGetPipelineCacheData) +XE_UI_VULKAN_FUNCTION(vkMapMemory) +XE_UI_VULKAN_FUNCTION(vkQueueSubmit) +XE_UI_VULKAN_FUNCTION(vkQueueWaitIdle) +XE_UI_VULKAN_FUNCTION(vkResetCommandBuffer) +XE_UI_VULKAN_FUNCTION(vkResetFences) +XE_UI_VULKAN_FUNCTION(vkUnmapMemory) +XE_UI_VULKAN_FUNCTION(vkUpdateDescriptorSets) +XE_UI_VULKAN_FUNCTION(vkWaitForFences) diff --git a/src/xenia/ui/vulkan/functions/device_amd_shader_info.inc b/src/xenia/ui/vulkan/functions/device_amd_shader_info.inc new file mode 100644 index 000000000..2da2b31b6 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/device_amd_shader_info.inc @@ -0,0 +1,2 @@ +// VK_AMD_shader_info functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkGetShaderInfoAMD) diff --git a/src/xenia/ui/vulkan/functions/device_ext_debug_marker.inc b/src/xenia/ui/vulkan/functions/device_ext_debug_marker.inc new file mode 100644 index 000000000..11a44aa81 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/device_ext_debug_marker.inc @@ -0,0 +1,5 @@ +// VK_EXT_debug_marker functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkDebugMarkerSetObjectNameEXT) +XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerBeginEXT) +XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerEndEXT) +XE_UI_VULKAN_FUNCTION(vkCmdDebugMarkerInsertEXT) diff --git a/src/xenia/ui/vulkan/functions/device_khr_swapchain.inc b/src/xenia/ui/vulkan/functions/device_khr_swapchain.inc new file mode 100644 index 000000000..435d88a82 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/device_khr_swapchain.inc @@ -0,0 +1,6 @@ +// VK_KHR_swapchain functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkAcquireNextImageKHR) +XE_UI_VULKAN_FUNCTION(vkCreateSwapchainKHR) +XE_UI_VULKAN_FUNCTION(vkDestroySwapchainKHR) +XE_UI_VULKAN_FUNCTION(vkGetSwapchainImagesKHR) +XE_UI_VULKAN_FUNCTION(vkQueuePresentKHR) diff --git a/src/xenia/ui/vulkan/functions/instance_1_0.inc b/src/xenia/ui/vulkan/functions/instance_1_0.inc new file mode 100644 index 000000000..8fbb7f8ca --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_1_0.inc @@ -0,0 +1,13 @@ +// Vulkan 1.0 core instance functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkCreateDevice) +XE_UI_VULKAN_FUNCTION(vkDestroyDevice) +XE_UI_VULKAN_FUNCTION(vkEnumerateDeviceExtensionProperties) +XE_UI_VULKAN_FUNCTION(vkEnumerateDeviceLayerProperties) +XE_UI_VULKAN_FUNCTION(vkEnumeratePhysicalDevices) +XE_UI_VULKAN_FUNCTION(vkGetDeviceProcAddr) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceFeatures) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceFormatProperties) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceImageFormatProperties) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceMemoryProperties) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceProperties) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties) diff --git a/src/xenia/ui/vulkan/functions/instance_ext_debug_report.inc b/src/xenia/ui/vulkan/functions/instance_ext_debug_report.inc new file mode 100644 index 000000000..4d57b9552 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_ext_debug_report.inc @@ -0,0 +1,3 @@ +// VK_EXT_debug_report functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkCreateDebugReportCallbackEXT) +XE_UI_VULKAN_FUNCTION(vkDestroyDebugReportCallbackEXT) diff --git a/src/xenia/ui/vulkan/functions/instance_khr_android_surface.inc b/src/xenia/ui/vulkan/functions/instance_khr_android_surface.inc new file mode 100644 index 000000000..7449ac2d1 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_khr_android_surface.inc @@ -0,0 +1,2 @@ +// VK_KHR_android_surface functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkCreateAndroidSurfaceKHR) diff --git a/src/xenia/ui/vulkan/functions/instance_khr_surface.inc b/src/xenia/ui/vulkan/functions/instance_khr_surface.inc new file mode 100644 index 000000000..ca3efdebf --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_khr_surface.inc @@ -0,0 +1,6 @@ +// VK_KHR_surface functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkDestroySurfaceKHR) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceCapabilitiesKHR) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceFormatsKHR) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfacePresentModesKHR) +XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR) diff --git a/src/xenia/ui/vulkan/functions/instance_khr_win32_surface.inc b/src/xenia/ui/vulkan/functions/instance_khr_win32_surface.inc new file mode 100644 index 000000000..bbda68af6 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_khr_win32_surface.inc @@ -0,0 +1,2 @@ +// VK_KHR_win32_surface functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkCreateWin32SurfaceKHR) diff --git a/src/xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc b/src/xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc new file mode 100644 index 000000000..4faa37b75 --- /dev/null +++ b/src/xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc @@ -0,0 +1,2 @@ +// VK_KHR_xcb_surface functions used in Xenia. +XE_UI_VULKAN_FUNCTION(vkCreateXcbSurfaceKHR) diff --git a/src/xenia/ui/vulkan/premake5.lua b/src/xenia/ui/vulkan/premake5.lua index d93f98af6..d9f02318c 100644 --- a/src/xenia/ui/vulkan/premake5.lua +++ b/src/xenia/ui/vulkan/premake5.lua @@ -18,6 +18,7 @@ project("xenia-ui-vulkan") project_root.."/third_party/vulkan/", }) local_platform_files() + local_platform_files("functions") files({ "shaders/bin/*.h", }) @@ -31,7 +32,6 @@ project("xenia-ui-window-vulkan-demo") links({ "fmt", "imgui", - "volk", "xenia-base", "xenia-ui", "xenia-ui-spirv", diff --git a/src/xenia/ui/vulkan/vulkan.h b/src/xenia/ui/vulkan/vulkan.h index cebd210ca..9ba44197c 100644 --- a/src/xenia/ui/vulkan/vulkan.h +++ b/src/xenia/ui/vulkan/vulkan.h @@ -10,20 +10,30 @@ #ifndef XENIA_UI_VULKAN_VULKAN_H_ #define XENIA_UI_VULKAN_VULKAN_H_ +#include "xenia/base/cvar.h" #include "xenia/base/platform.h" -#if XE_PLATFORM_WIN32 -#define VK_USE_PLATFORM_WIN32_KHR 1 -#elif XE_PLATFORM_LINUX +#if XE_PLATFORM_ANDROID +#ifndef VK_USE_PLATFORM_ANDROID_KHR +#define VK_USE_PLATFORM_ANDROID_KHR 1 +#endif +#elif XE_PLATFORM_GNU_LINUX +#ifndef VK_USE_PLATFORM_XCB_KHR #define VK_USE_PLATFORM_XCB_KHR 1 -#else -#error Platform not yet supported. -#endif // XE_PLATFORM_WIN32 +#endif +#elif XE_PLATFORM_WIN32 +// Must be included before vulkan.h with VK_USE_PLATFORM_WIN32_KHR because it +// includes Windows.h too. +#include "xenia/base/platform_win.h" +#ifndef VK_USE_PLATFORM_WIN32_KHR +#define VK_USE_PLATFORM_WIN32_KHR 1 +#endif +#endif -// We use a loader with its own function prototypes. -#include "third_party/volk/volk.h" +#ifndef VK_NO_PROTOTYPES +#define VK_NO_PROTOTYPES 1 +#endif #include "third_party/vulkan/vulkan.h" -#include "xenia/base/cvar.h" #define XELOGVK XELOGI diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index 50f51ad74..d7c1cff4d 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -41,10 +41,11 @@ VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window) VulkanContext::~VulkanContext() { VkResult status; auto provider = static_cast(provider_); - auto device = provider->device(); + VulkanDevice* device = provider->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); { std::lock_guard queue_lock(device->primary_queue_mutex()); - status = vkQueueWaitIdle(device->primary_queue()); + status = dfn.vkQueueWaitIdle(device->primary_queue()); } immediate_drawer_.reset(); swap_chain_.reset(); @@ -52,7 +53,8 @@ VulkanContext::~VulkanContext() { bool VulkanContext::Initialize() { auto provider = static_cast(provider_); - auto device = provider->device(); + VulkanInstance* instance = provider->instance(); + const VulkanInstance::InstanceFunctions& ifn = instance->ifn(); if (target_window_) { // Create swap chain used to present to the window. @@ -66,8 +68,8 @@ bool VulkanContext::Initialize() { create_info.hinstance = static_cast(target_window_->native_platform_handle()); create_info.hwnd = static_cast(target_window_->native_handle()); - status = vkCreateWin32SurfaceKHR(*provider->instance(), &create_info, - nullptr, &surface); + status = ifn.vkCreateWin32SurfaceKHR(*provider->instance(), &create_info, + nullptr, &surface); CheckResult(status, "vkCreateWin32SurfaceKHR"); #elif XE_PLATFORM_LINUX #ifdef GDK_WINDOWING_X11 @@ -86,8 +88,8 @@ bool VulkanContext::Initialize() { create_info.connection = static_cast( target_window_->native_platform_handle()); create_info.window = static_cast(window); - status = vkCreateXcbSurfaceKHR(*provider->instance(), &create_info, nullptr, - &surface); + status = ifn.vkCreateXcbSurfaceKHR(*provider->instance(), &create_info, + nullptr, &surface); CheckResult(status, "vkCreateXcbSurfaceKHR"); #else #error Unsupported GDK Backend on Linux. @@ -100,8 +102,8 @@ bool VulkanContext::Initialize() { return false; } - swap_chain_ = std::make_unique(provider->instance(), - provider->device()); + swap_chain_ = + std::make_unique(instance, provider->device()); if (swap_chain_->Initialize(surface) != VK_SUCCESS) { XELOGE("Unable to initialize swap chain"); return false; @@ -144,7 +146,8 @@ void VulkanContext::ClearCurrent() {} bool VulkanContext::BeginSwap() { SCOPE_profile_cpu_f("gpu"); auto provider = static_cast(provider_); - auto device = provider->device(); + VulkanDevice* device = provider->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); VkResult status; @@ -169,15 +172,16 @@ bool VulkanContext::BeginSwap() { // TODO(benvanik): use a fence instead? May not be possible with target image. std::lock_guard queue_lock(device->primary_queue_mutex()); - status = vkQueueWaitIdle(device->primary_queue()); + status = dfn.vkQueueWaitIdle(device->primary_queue()); return true; } void VulkanContext::EndSwap() { SCOPE_profile_cpu_f("gpu"); - auto provider = static_cast(provider_); - auto device = provider->device(); + auto provider = static_cast(provider_); + VulkanDevice* device = provider->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); VkResult status; @@ -193,7 +197,7 @@ void VulkanContext::EndSwap() { // Wait until the queue is idle. // TODO(benvanik): is this required? std::lock_guard queue_lock(device->primary_queue_mutex()); - status = vkQueueWaitIdle(device->primary_queue()); + status = dfn.vkQueueWaitIdle(device->primary_queue()); } std::unique_ptr VulkanContext::Capture() { diff --git a/src/xenia/ui/vulkan/vulkan_device.cc b/src/xenia/ui/vulkan/vulkan_device.cc index 275a45070..704e43c2d 100644 --- a/src/xenia/ui/vulkan/vulkan_device.cc +++ b/src/xenia/ui/vulkan/vulkan_device.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -68,7 +69,8 @@ VulkanDevice::VulkanDevice(VulkanInstance* instance) : instance_(instance) { VulkanDevice::~VulkanDevice() { if (handle) { - vkDestroyDevice(handle, nullptr); + const VulkanInstance::InstanceFunctions& ifn = instance_->ifn(); + ifn.vkDestroyDevice(handle, nullptr); handle = nullptr; } } @@ -91,9 +93,11 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) { return false; } + const VulkanInstance::InstanceFunctions& ifn = instance_->ifn(); + // Query supported features so we can make sure we have what we need. VkPhysicalDeviceFeatures supported_features; - vkGetPhysicalDeviceFeatures(device_info.handle, &supported_features); + ifn.vkGetPhysicalDeviceFeatures(device_info.handle, &supported_features); VkPhysicalDeviceFeatures enabled_features = {0}; bool any_features_missing = false; #define ENABLE_AND_EXPECT(name) \ @@ -189,7 +193,8 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) { create_info.ppEnabledExtensionNames = enabled_extensions_.data(); create_info.pEnabledFeatures = &enabled_features; - auto err = vkCreateDevice(device_info.handle, &create_info, nullptr, &handle); + auto err = + ifn.vkCreateDevice(device_info.handle, &create_info, nullptr, &handle); switch (err) { case VK_SUCCESS: // Ok! @@ -211,30 +216,34 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) { return false; } - // Set flags so we can track enabled extensions easily. - for (auto& ext : enabled_extensions_) { - if (!std::strcmp(ext, VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) { - debug_marker_ena_ = true; - pfn_vkDebugMarkerSetObjectNameEXT_ = - (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr( - *this, "vkDebugMarkerSetObjectNameEXT"); - pfn_vkCmdDebugMarkerBeginEXT_ = - (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr( - *this, "vkCmdDebugMarkerBeginEXT"); - pfn_vkCmdDebugMarkerEndEXT_ = - (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr( - *this, "vkCmdDebugMarkerEndEXT"); - pfn_vkCmdDebugMarkerInsertEXT_ = - (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr( - *this, "vkCmdDebugMarkerInsertEXT"); - } + // Get device functions. + std::memset(&dfn_, 0, sizeof(dfn_)); + bool device_functions_loaded = true; + debug_marker_ena_ = false; +#define XE_UI_VULKAN_FUNCTION(name) \ + device_functions_loaded &= \ + (dfn_.name = PFN_##name(ifn.vkGetDeviceProcAddr(handle, #name))) != \ + nullptr; +#include "xenia/ui/vulkan/functions/device_1_0.inc" +#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc" + if (HasEnabledExtension(VK_AMD_SHADER_INFO_EXTENSION_NAME)) { +#include "xenia/ui/vulkan/functions/device_amd_shader_info.inc" + } + debug_marker_ena_ = HasEnabledExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); + if (debug_marker_ena_) { +#include "xenia/ui/vulkan/functions/device_ext_debug_marker.inc" + } +#undef XE_UI_VULKAN_FUNCTION + if (!device_functions_loaded) { + XELOGE("Failed to get Vulkan device function pointers"); + return false; } device_info_ = std::move(device_info); queue_family_index_ = ideal_queue_family_index; // Get the primary queue used for most submissions/etc. - vkGetDeviceQueue(handle, queue_family_index_, 0, &primary_queue_); + dfn_.vkGetDeviceQueue(handle, queue_family_index_, 0, &primary_queue_); if (!primary_queue_) { XELOGE("vkGetDeviceQueue returned nullptr!"); return false; @@ -253,7 +262,7 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) { continue; } - vkGetDeviceQueue(handle, i, j, &queue); + dfn_.vkGetDeviceQueue(handle, i, j, &queue); if (queue) { free_queues_[i].push_back(queue); } @@ -292,8 +301,8 @@ void VulkanDevice::ReleaseQueue(VkQueue queue, uint32_t queue_family_index) { void VulkanDevice::DbgSetObjectName(uint64_t object, VkDebugReportObjectTypeEXT object_type, - std::string name) { - if (!debug_marker_ena_ || pfn_vkDebugMarkerSetObjectNameEXT_ == nullptr) { + const std::string& name) const { + if (!debug_marker_ena_) { // Extension disabled. return; } @@ -304,13 +313,13 @@ void VulkanDevice::DbgSetObjectName(uint64_t object, info.objectType = object_type; info.object = object; info.pObjectName = name.c_str(); - pfn_vkDebugMarkerSetObjectNameEXT_(*this, &info); + dfn_.vkDebugMarkerSetObjectNameEXT(*this, &info); } void VulkanDevice::DbgMarkerBegin(VkCommandBuffer command_buffer, std::string name, float r, float g, float b, - float a) { - if (!debug_marker_ena_ || pfn_vkCmdDebugMarkerBeginEXT_ == nullptr) { + float a) const { + if (!debug_marker_ena_) { // Extension disabled. return; } @@ -323,22 +332,22 @@ void VulkanDevice::DbgMarkerBegin(VkCommandBuffer command_buffer, info.color[1] = g; info.color[2] = b; info.color[3] = a; - pfn_vkCmdDebugMarkerBeginEXT_(command_buffer, &info); + dfn_.vkCmdDebugMarkerBeginEXT(command_buffer, &info); } -void VulkanDevice::DbgMarkerEnd(VkCommandBuffer command_buffer) { - if (!debug_marker_ena_ || pfn_vkCmdDebugMarkerEndEXT_ == nullptr) { +void VulkanDevice::DbgMarkerEnd(VkCommandBuffer command_buffer) const { + if (!debug_marker_ena_) { // Extension disabled. return; } - pfn_vkCmdDebugMarkerEndEXT_(command_buffer); + dfn_.vkCmdDebugMarkerEndEXT(command_buffer); } void VulkanDevice::DbgMarkerInsert(VkCommandBuffer command_buffer, std::string name, float r, float g, float b, - float a) { - if (!debug_marker_ena_ || pfn_vkCmdDebugMarkerInsertEXT_ == nullptr) { + float a) const { + if (!debug_marker_ena_) { // Extension disabled. return; } @@ -351,7 +360,7 @@ void VulkanDevice::DbgMarkerInsert(VkCommandBuffer command_buffer, info.color[1] = g; info.color[2] = g; info.color[3] = b; - pfn_vkCmdDebugMarkerInsertEXT_(command_buffer, &info); + dfn_.vkCmdDebugMarkerInsertEXT(command_buffer, &info); } bool VulkanDevice::is_renderdoc_attached() const { @@ -379,7 +388,8 @@ void VulkanDevice::EndRenderDocFrameCapture() { } VkDeviceMemory VulkanDevice::AllocateMemory( - const VkMemoryRequirements& requirements, VkFlags required_properties) { + const VkMemoryRequirements& requirements, + VkFlags required_properties) const { // Search memory types to find one matching our requirements and our // properties. uint32_t type_index = UINT_MAX; @@ -407,7 +417,7 @@ VkDeviceMemory VulkanDevice::AllocateMemory( memory_info.allocationSize = requirements.size; memory_info.memoryTypeIndex = type_index; VkDeviceMemory memory = nullptr; - auto err = vkAllocateMemory(handle, &memory_info, nullptr, &memory); + auto err = dfn_.vkAllocateMemory(handle, &memory_info, nullptr, &memory); CheckResult(err, "vkAllocateMemory"); return memory; } diff --git a/src/xenia/ui/vulkan/vulkan_device.h b/src/xenia/ui/vulkan/vulkan_device.h index 498e6da28..30af10040 100644 --- a/src/xenia/ui/vulkan/vulkan_device.h +++ b/src/xenia/ui/vulkan/vulkan_device.h @@ -32,11 +32,23 @@ class VulkanDevice { VulkanDevice(VulkanInstance* instance); ~VulkanDevice(); + VulkanInstance* instance() const { return instance_; } + VkDevice handle = nullptr; operator VkDevice() const { return handle; } operator VkPhysicalDevice() const { return device_info_.handle; } + struct DeviceFunctions { +#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name; +#include "xenia/ui/vulkan/functions/device_1_0.inc" +#include "xenia/ui/vulkan/functions/device_amd_shader_info.inc" +#include "xenia/ui/vulkan/functions/device_ext_debug_marker.inc" +#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc" +#undef XE_UI_VULKAN_FUNCTION + }; + const DeviceFunctions& dfn() const { return dfn_; } + // Declares a layer to verify and enable upon initialization. // Must be called before Initialize. void DeclareRequiredLayer(std::string name, uint32_t min_version, @@ -78,16 +90,16 @@ class VulkanDevice { void ReleaseQueue(VkQueue queue, uint32_t queue_family_index); void DbgSetObjectName(uint64_t object, VkDebugReportObjectTypeEXT object_type, - std::string name); + const std::string& name) const; void DbgMarkerBegin(VkCommandBuffer command_buffer, std::string name, float r = 0.0f, float g = 0.0f, float b = 0.0f, - float a = 0.0f); - void DbgMarkerEnd(VkCommandBuffer command_buffer); + float a = 0.0f) const; + void DbgMarkerEnd(VkCommandBuffer command_buffer) const; void DbgMarkerInsert(VkCommandBuffer command_buffer, std::string name, float r = 0.0f, float g = 0.0f, float b = 0.0f, - float a = 0.0f); + float a = 0.0f) const; // True if RenderDoc is attached and available for use. bool is_renderdoc_attached() const; @@ -101,7 +113,7 @@ class VulkanDevice { // Allocates memory of the given size matching the required properties. VkDeviceMemory AllocateMemory( const VkMemoryRequirements& requirements, - VkFlags required_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + VkFlags required_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) const; private: VulkanInstance* instance_ = nullptr; @@ -110,6 +122,8 @@ class VulkanDevice { std::vector required_extensions_; std::vector enabled_extensions_; + DeviceFunctions dfn_ = {}; + bool debug_marker_ena_ = false; PFN_vkDebugMarkerSetObjectNameEXT pfn_vkDebugMarkerSetObjectNameEXT_ = nullptr; diff --git a/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc b/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc index a7aac5eae..6d95143ba 100644 --- a/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc +++ b/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc @@ -29,9 +29,11 @@ constexpr uint32_t kCircularBufferCapacity = 2 * 1024 * 1024; class LightweightCircularBuffer { public: - LightweightCircularBuffer(VulkanDevice* device) : device_(*device) { + LightweightCircularBuffer(const VulkanDevice* device) : device_(*device) { buffer_capacity_ = xe::round_up(kCircularBufferCapacity, 4096); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); + // Index buffer. VkBufferCreateInfo index_buffer_info; index_buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; @@ -42,8 +44,8 @@ class LightweightCircularBuffer { index_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; index_buffer_info.queueFamilyIndexCount = 0; index_buffer_info.pQueueFamilyIndices = nullptr; - auto status = - vkCreateBuffer(device_, &index_buffer_info, nullptr, &index_buffer_); + auto status = dfn.vkCreateBuffer(device_, &index_buffer_info, nullptr, + &index_buffer_); CheckResult(status, "vkCreateBuffer"); // Vertex buffer. @@ -56,34 +58,37 @@ class LightweightCircularBuffer { vertex_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vertex_buffer_info.queueFamilyIndexCount = 0; vertex_buffer_info.pQueueFamilyIndices = nullptr; - status = - vkCreateBuffer(*device, &vertex_buffer_info, nullptr, &vertex_buffer_); + status = dfn.vkCreateBuffer(*device, &vertex_buffer_info, nullptr, + &vertex_buffer_); CheckResult(status, "vkCreateBuffer"); // Allocate underlying buffer. // We alias it for both vertices and indices. VkMemoryRequirements buffer_requirements; - vkGetBufferMemoryRequirements(device_, index_buffer_, &buffer_requirements); + dfn.vkGetBufferMemoryRequirements(device_, index_buffer_, + &buffer_requirements); buffer_memory_ = device->AllocateMemory( buffer_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - vkBindBufferMemory(*device, index_buffer_, buffer_memory_, 0); - vkBindBufferMemory(*device, vertex_buffer_, buffer_memory_, 0); + dfn.vkBindBufferMemory(*device, index_buffer_, buffer_memory_, 0); + dfn.vkBindBufferMemory(*device, vertex_buffer_, buffer_memory_, 0); // Persistent mapping. - status = vkMapMemory(device_, buffer_memory_, 0, VK_WHOLE_SIZE, 0, - &buffer_data_); + status = dfn.vkMapMemory(device_, buffer_memory_, 0, VK_WHOLE_SIZE, 0, + &buffer_data_); CheckResult(status, "vkMapMemory"); } ~LightweightCircularBuffer() { + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); + if (buffer_memory_) { - vkUnmapMemory(device_, buffer_memory_); + dfn.vkUnmapMemory(device_, buffer_memory_); buffer_memory_ = nullptr; } - VK_SAFE_DESTROY(vkDestroyBuffer, device_, index_buffer_, nullptr); - VK_SAFE_DESTROY(vkDestroyBuffer, device_, vertex_buffer_, nullptr); - VK_SAFE_DESTROY(vkFreeMemory, device_, buffer_memory_, nullptr); + DestroyAndNullHandle(dfn.vkDestroyBuffer, device_, index_buffer_); + DestroyAndNullHandle(dfn.vkDestroyBuffer, device_, vertex_buffer_); + DestroyAndNullHandle(dfn.vkFreeMemory, device_, buffer_memory_); } VkBuffer vertex_buffer() const { return vertex_buffer_; } @@ -118,18 +123,19 @@ class LightweightCircularBuffer { // Flush memory. // TODO(benvanik): do only in large batches? can barrier it. + const VulkanDevice::DeviceFunctions& dfn = device_.dfn(); VkMappedMemoryRange dirty_range; dirty_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; dirty_range.pNext = nullptr; dirty_range.memory = buffer_memory_; dirty_range.offset = offset; dirty_range.size = source_length; - vkFlushMappedMemoryRanges(device_, 1, &dirty_range); + dfn.vkFlushMappedMemoryRanges(device_, 1, &dirty_range); return offset; } private: - VkDevice device_ = nullptr; + const VulkanDevice& device_; VkBuffer index_buffer_ = nullptr; VkBuffer vertex_buffer_ = nullptr; @@ -153,6 +159,7 @@ class VulkanImmediateTexture : public ImmediateTexture { VkResult Initialize(VkDescriptorSetLayout descriptor_set_layout, VkImageView image_view) { image_view_ = image_view; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Create descriptor set used just for this texture. @@ -163,8 +170,8 @@ class VulkanImmediateTexture : public ImmediateTexture { set_alloc_info.descriptorPool = descriptor_pool_; set_alloc_info.descriptorSetCount = 1; set_alloc_info.pSetLayouts = &descriptor_set_layout; - status = - vkAllocateDescriptorSets(*device_, &set_alloc_info, &descriptor_set_); + status = dfn.vkAllocateDescriptorSets(*device_, &set_alloc_info, + &descriptor_set_); CheckResult(status, "vkAllocateDescriptorSets"); if (status != VK_SUCCESS) { return status; @@ -184,12 +191,13 @@ class VulkanImmediateTexture : public ImmediateTexture { descriptor_write.descriptorCount = 1; descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptor_write.pImageInfo = &texture_info; - vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); + dfn.vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); return VK_SUCCESS; } VkResult Initialize(VkDescriptorSetLayout descriptor_set_layout) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Create image object. @@ -209,7 +217,7 @@ class VulkanImmediateTexture : public ImmediateTexture { image_info.queueFamilyIndexCount = 0; image_info.pQueueFamilyIndices = nullptr; image_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - status = vkCreateImage(*device_, &image_info, nullptr, &image_); + status = dfn.vkCreateImage(*device_, &image_info, nullptr, &image_); CheckResult(status, "vkCreateImage"); if (status != VK_SUCCESS) { return status; @@ -217,7 +225,7 @@ class VulkanImmediateTexture : public ImmediateTexture { // Allocate memory for the image. VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(*device_, image_, &memory_requirements); + dfn.vkGetImageMemoryRequirements(*device_, image_, &memory_requirements); device_memory_ = device_->AllocateMemory( memory_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); if (!device_memory_) { @@ -225,7 +233,7 @@ class VulkanImmediateTexture : public ImmediateTexture { } // Bind memory and the image together. - status = vkBindImageMemory(*device_, image_, device_memory_, 0); + status = dfn.vkBindImageMemory(*device_, image_, device_memory_, 0); CheckResult(status, "vkBindImageMemory"); if (status != VK_SUCCESS) { return status; @@ -246,7 +254,7 @@ class VulkanImmediateTexture : public ImmediateTexture { VK_COMPONENT_SWIZZLE_A, }; view_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - status = vkCreateImageView(*device_, &view_info, nullptr, &image_view_); + status = dfn.vkCreateImageView(*device_, &view_info, nullptr, &image_view_); CheckResult(status, "vkCreateImageView"); if (status != VK_SUCCESS) { return status; @@ -260,8 +268,8 @@ class VulkanImmediateTexture : public ImmediateTexture { set_alloc_info.descriptorPool = descriptor_pool_; set_alloc_info.descriptorSetCount = 1; set_alloc_info.pSetLayouts = &descriptor_set_layout; - status = - vkAllocateDescriptorSets(*device_, &set_alloc_info, &descriptor_set_); + status = dfn.vkAllocateDescriptorSets(*device_, &set_alloc_info, + &descriptor_set_); CheckResult(status, "vkAllocateDescriptorSets"); if (status != VK_SUCCESS) { return status; @@ -281,44 +289,48 @@ class VulkanImmediateTexture : public ImmediateTexture { descriptor_write.descriptorCount = 1; descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptor_write.pImageInfo = &texture_info; - vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); + dfn.vkUpdateDescriptorSets(*device_, 1, &descriptor_write, 0, nullptr); return VK_SUCCESS; } void Shutdown() { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + if (descriptor_set_) { - vkFreeDescriptorSets(*device_, descriptor_pool_, 1, &descriptor_set_); + dfn.vkFreeDescriptorSets(*device_, descriptor_pool_, 1, &descriptor_set_); descriptor_set_ = nullptr; } - VK_SAFE_DESTROY(vkDestroyImageView, *device_, image_view_, nullptr); - VK_SAFE_DESTROY(vkDestroyImage, *device_, image_, nullptr); - VK_SAFE_DESTROY(vkFreeMemory, *device_, device_memory_, nullptr); + DestroyAndNullHandle(dfn.vkDestroyImageView, *device_, image_view_); + DestroyAndNullHandle(dfn.vkDestroyImage, *device_, image_); + DestroyAndNullHandle(dfn.vkFreeMemory, *device_, device_memory_); } VkResult Upload(const uint8_t* src_data) { // TODO(benvanik): assert not in use? textures aren't dynamic right now. + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + // Get device image layout. VkImageSubresource subresource; subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresource.mipLevel = 0; subresource.arrayLayer = 0; VkSubresourceLayout layout; - vkGetImageSubresourceLayout(*device_, image_, &subresource, &layout); + dfn.vkGetImageSubresourceLayout(*device_, image_, &subresource, &layout); // Map memory for upload. uint8_t* gpu_data = nullptr; - auto status = vkMapMemory(*device_, device_memory_, 0, layout.size, 0, - reinterpret_cast(&gpu_data)); + auto status = dfn.vkMapMemory(*device_, device_memory_, 0, layout.size, 0, + reinterpret_cast(&gpu_data)); CheckResult(status, "vkMapMemory"); if (status == VK_SUCCESS) { // Copy the entire texture, hoping its layout matches what we expect. std::memcpy(gpu_data + layout.offset, src_data, layout.size); - vkUnmapMemory(*device_, device_memory_); + dfn.vkUnmapMemory(*device_, device_memory_); } return status; @@ -328,6 +340,8 @@ class VulkanImmediateTexture : public ImmediateTexture { // the command buffer WILL be queued and executed by the device. void TransitionLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + VkImageMemoryBarrier image_barrier; image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; image_barrier.pNext = nullptr; @@ -342,16 +356,16 @@ class VulkanImmediateTexture : public ImmediateTexture { image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_layout_ = new_layout; - vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &image_barrier); + dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, + 0, nullptr, 1, &image_barrier); } VkDescriptorSet descriptor_set() const { return descriptor_set_; } VkImageLayout layout() const { return image_layout_; } private: - ui::vulkan::VulkanDevice* device_ = nullptr; + VulkanDevice* device_ = nullptr; VkDescriptorPool descriptor_pool_ = nullptr; VkSampler sampler_ = nullptr; // Not owned. VkImage image_ = nullptr; @@ -367,7 +381,8 @@ VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext* graphics_context) VulkanImmediateDrawer::~VulkanImmediateDrawer() { Shutdown(); } VkResult VulkanImmediateDrawer::Initialize() { - auto device = context_->device(); + const VulkanDevice* device = context_->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); // NEAREST + CLAMP VkSamplerCreateInfo sampler_info; @@ -389,8 +404,8 @@ VkResult VulkanImmediateDrawer::Initialize() { sampler_info.maxLod = 0.0f; sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; sampler_info.unnormalizedCoordinates = VK_FALSE; - auto status = vkCreateSampler(*device, &sampler_info, nullptr, - &samplers_.nearest_clamp); + auto status = dfn.vkCreateSampler(*device, &sampler_info, nullptr, + &samplers_.nearest_clamp); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -400,8 +415,8 @@ VkResult VulkanImmediateDrawer::Initialize() { sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - status = vkCreateSampler(*device, &sampler_info, nullptr, - &samplers_.nearest_repeat); + status = dfn.vkCreateSampler(*device, &sampler_info, nullptr, + &samplers_.nearest_repeat); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -413,8 +428,8 @@ VkResult VulkanImmediateDrawer::Initialize() { sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - status = - vkCreateSampler(*device, &sampler_info, nullptr, &samplers_.linear_clamp); + status = dfn.vkCreateSampler(*device, &sampler_info, nullptr, + &samplers_.linear_clamp); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -424,8 +439,8 @@ VkResult VulkanImmediateDrawer::Initialize() { sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - status = vkCreateSampler(*device, &sampler_info, nullptr, - &samplers_.linear_repeat); + status = dfn.vkCreateSampler(*device, &sampler_info, nullptr, + &samplers_.linear_repeat); CheckResult(status, "vkCreateSampler"); if (status != VK_SUCCESS) { return status; @@ -447,8 +462,8 @@ VkResult VulkanImmediateDrawer::Initialize() { texture_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; texture_binding.pImmutableSamplers = nullptr; texture_set_layout_info.pBindings = &texture_binding; - status = vkCreateDescriptorSetLayout(*device, &texture_set_layout_info, - nullptr, &texture_set_layout_); + status = dfn.vkCreateDescriptorSetLayout(*device, &texture_set_layout_info, + nullptr, &texture_set_layout_); CheckResult(status, "vkCreateDescriptorSetLayout"); if (status != VK_SUCCESS) { return status; @@ -468,8 +483,8 @@ VkResult VulkanImmediateDrawer::Initialize() { pool_sizes[0].descriptorCount = 128; descriptor_pool_info.poolSizeCount = 1; descriptor_pool_info.pPoolSizes = pool_sizes; - status = vkCreateDescriptorPool(*device, &descriptor_pool_info, nullptr, - &descriptor_pool_); + status = dfn.vkCreateDescriptorPool(*device, &descriptor_pool_info, nullptr, + &descriptor_pool_); CheckResult(status, "vkCreateDescriptorPool"); if (status != VK_SUCCESS) { return status; @@ -495,8 +510,8 @@ VkResult VulkanImmediateDrawer::Initialize() { pipeline_layout_info.pushConstantRangeCount = static_cast(xe::countof(push_constant_ranges)); pipeline_layout_info.pPushConstantRanges = push_constant_ranges; - status = vkCreatePipelineLayout(*device, &pipeline_layout_info, nullptr, - &pipeline_layout_); + status = dfn.vkCreatePipelineLayout(*device, &pipeline_layout_info, nullptr, + &pipeline_layout_); CheckResult(status, "vkCreatePipelineLayout"); if (status != VK_SUCCESS) { return status; @@ -510,8 +525,8 @@ VkResult VulkanImmediateDrawer::Initialize() { vertex_shader_info.codeSize = sizeof(immediate_vert); vertex_shader_info.pCode = reinterpret_cast(immediate_vert); VkShaderModule vertex_shader; - status = vkCreateShaderModule(*device, &vertex_shader_info, nullptr, - &vertex_shader); + status = dfn.vkCreateShaderModule(*device, &vertex_shader_info, nullptr, + &vertex_shader); CheckResult(status, "vkCreateShaderModule"); VkShaderModuleCreateInfo fragment_shader_info; fragment_shader_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; @@ -521,8 +536,8 @@ VkResult VulkanImmediateDrawer::Initialize() { fragment_shader_info.pCode = reinterpret_cast(immediate_frag); VkShaderModule fragment_shader; - status = vkCreateShaderModule(*device, &fragment_shader_info, nullptr, - &fragment_shader); + status = dfn.vkCreateShaderModule(*device, &fragment_shader_info, nullptr, + &fragment_shader); CheckResult(status, "vkCreateShaderModule"); // Pipeline used when rendering triangles. @@ -667,8 +682,8 @@ VkResult VulkanImmediateDrawer::Initialize() { pipeline_info.basePipelineHandle = nullptr; pipeline_info.basePipelineIndex = -1; if (status == VK_SUCCESS) { - status = vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, - nullptr, &triangle_pipeline_); + status = dfn.vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, + nullptr, &triangle_pipeline_); CheckResult(status, "vkCreateGraphicsPipelines"); } @@ -678,13 +693,13 @@ VkResult VulkanImmediateDrawer::Initialize() { pipeline_info.basePipelineHandle = triangle_pipeline_; pipeline_info.basePipelineIndex = -1; if (status == VK_SUCCESS) { - status = vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, - nullptr, &line_pipeline_); + status = dfn.vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, + nullptr, &line_pipeline_); CheckResult(status, "vkCreateGraphicsPipelines"); } - VK_SAFE_DESTROY(vkDestroyShaderModule, *device, vertex_shader, nullptr); - VK_SAFE_DESTROY(vkDestroyShaderModule, *device, fragment_shader, nullptr); + DestroyAndNullHandle(dfn.vkDestroyShaderModule, *device, vertex_shader); + DestroyAndNullHandle(dfn.vkDestroyShaderModule, *device, fragment_shader); // Allocate the buffer we'll use for our vertex and index data. circular_buffer_ = std::make_unique(device); @@ -693,22 +708,23 @@ VkResult VulkanImmediateDrawer::Initialize() { } void VulkanImmediateDrawer::Shutdown() { - auto device = context_->device(); + const VulkanDevice* device = context_->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); circular_buffer_.reset(); - VK_SAFE_DESTROY(vkDestroyPipeline, *device, line_pipeline_, nullptr); - VK_SAFE_DESTROY(vkDestroyPipeline, *device, triangle_pipeline_, nullptr); - VK_SAFE_DESTROY(vkDestroyPipelineLayout, *device, pipeline_layout_, nullptr); + DestroyAndNullHandle(dfn.vkDestroyPipeline, *device, line_pipeline_); + DestroyAndNullHandle(dfn.vkDestroyPipeline, *device, triangle_pipeline_); + DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, *device, pipeline_layout_); - VK_SAFE_DESTROY(vkDestroyDescriptorPool, *device, descriptor_pool_, nullptr); - VK_SAFE_DESTROY(vkDestroyDescriptorSetLayout, *device, texture_set_layout_, - nullptr); + DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, *device, descriptor_pool_); + DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, *device, + texture_set_layout_); - VK_SAFE_DESTROY(vkDestroySampler, *device, samplers_.nearest_clamp, nullptr); - VK_SAFE_DESTROY(vkDestroySampler, *device, samplers_.nearest_repeat, nullptr); - VK_SAFE_DESTROY(vkDestroySampler, *device, samplers_.linear_clamp, nullptr); - VK_SAFE_DESTROY(vkDestroySampler, *device, samplers_.linear_repeat, nullptr); + DestroyAndNullHandle(dfn.vkDestroySampler, *device, samplers_.nearest_clamp); + DestroyAndNullHandle(dfn.vkDestroySampler, *device, samplers_.nearest_repeat); + DestroyAndNullHandle(dfn.vkDestroySampler, *device, samplers_.linear_clamp); + DestroyAndNullHandle(dfn.vkDestroySampler, *device, samplers_.linear_repeat); } std::unique_ptr VulkanImmediateDrawer::CreateTexture( @@ -751,7 +767,8 @@ std::unique_ptr VulkanImmediateDrawer::WrapTexture( void VulkanImmediateDrawer::Begin(int render_target_width, int render_target_height) { - auto device = context_->device(); + const VulkanDevice* device = context_->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); auto swap_chain = context_->swap_chain(); assert_null(current_cmd_buffer_); current_cmd_buffer_ = swap_chain->render_cmd_buffer(); @@ -766,7 +783,7 @@ void VulkanImmediateDrawer::Begin(int render_target_width, viewport.height = static_cast(render_target_height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; - vkCmdSetViewport(current_cmd_buffer_, 0, 1, &viewport); + dfn.vkCmdSetViewport(current_cmd_buffer_, 0, 1, &viewport); // Update projection matrix. const float ortho_projection[4][4] = { @@ -775,13 +792,14 @@ void VulkanImmediateDrawer::Begin(int render_target_width, {0.0f, 0.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f, 1.0f}, }; - vkCmdPushConstants(current_cmd_buffer_, pipeline_layout_, - VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 16, - ortho_projection); + dfn.vkCmdPushConstants(current_cmd_buffer_, pipeline_layout_, + VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 16, + ortho_projection); } void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) { - auto device = context_->device(); + const VulkanDevice* device = context_->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); // Upload vertices. VkDeviceSize vertices_offset = circular_buffer_->Emplace( @@ -791,8 +809,8 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) { return; } auto vertex_buffer = circular_buffer_->vertex_buffer(); - vkCmdBindVertexBuffers(current_cmd_buffer_, 0, 1, &vertex_buffer, - &vertices_offset); + dfn.vkCmdBindVertexBuffers(current_cmd_buffer_, 0, 1, &vertex_buffer, + &vertices_offset); // Upload indices. if (batch.indices) { @@ -802,22 +820,27 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) { // TODO(benvanik): die? return; } - vkCmdBindIndexBuffer(current_cmd_buffer_, circular_buffer_->index_buffer(), - indices_offset, VK_INDEX_TYPE_UINT16); + dfn.vkCmdBindIndexBuffer(current_cmd_buffer_, + circular_buffer_->index_buffer(), indices_offset, + VK_INDEX_TYPE_UINT16); } batch_has_index_buffer_ = !!batch.indices; } void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) { + const VulkanDevice* device = context_->device(); + const VulkanDevice::DeviceFunctions& dfn = device->dfn(); + switch (draw.primitive_type) { case ImmediatePrimitiveType::kLines: - vkCmdBindPipeline(current_cmd_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, - line_pipeline_); + dfn.vkCmdBindPipeline(current_cmd_buffer_, + VK_PIPELINE_BIND_POINT_GRAPHICS, line_pipeline_); break; case ImmediatePrimitiveType::kTriangles: - vkCmdBindPipeline(current_cmd_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, - triangle_pipeline_); + dfn.vkCmdBindPipeline(current_cmd_buffer_, + VK_PIPELINE_BIND_POINT_GRAPHICS, + triangle_pipeline_); break; } @@ -833,18 +856,18 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) { XELOGW("Failed to acquire texture descriptor set for immediate drawer!"); } - vkCmdBindDescriptorSets(current_cmd_buffer_, - VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_, - 0, 1, &texture_set, 0, nullptr); + dfn.vkCmdBindDescriptorSets( + current_cmd_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_, + 0, 1, &texture_set, 0, nullptr); } // Use push constants for our per-draw changes. // Here, the restrict_texture_samples uniform (was used before September 26, // 2020, now deleted). int restrict_texture_samples = 0; - vkCmdPushConstants(current_cmd_buffer_, pipeline_layout_, - VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 16, - sizeof(int), &restrict_texture_samples); + dfn.vkCmdPushConstants(current_cmd_buffer_, pipeline_layout_, + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 16, + sizeof(int), &restrict_texture_samples); // Scissor, if enabled. // Scissor can be disabled by making it the full screen. @@ -861,14 +884,14 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) { scissor.extent.width = current_render_target_width_; scissor.extent.height = current_render_target_height_; } - vkCmdSetScissor(current_cmd_buffer_, 0, 1, &scissor); + dfn.vkCmdSetScissor(current_cmd_buffer_, 0, 1, &scissor); // Issue draw. if (batch_has_index_buffer_) { - vkCmdDrawIndexed(current_cmd_buffer_, draw.count, 1, draw.index_offset, - draw.base_vertex, 0); + dfn.vkCmdDrawIndexed(current_cmd_buffer_, draw.count, 1, draw.index_offset, + draw.base_vertex, 0); } else { - vkCmdDraw(current_cmd_buffer_, draw.count, 1, draw.base_vertex, 0); + dfn.vkCmdDraw(current_cmd_buffer_, draw.count, 1, draw.base_vertex, 0); } } diff --git a/src/xenia/ui/vulkan/vulkan_instance.cc b/src/xenia/ui/vulkan/vulkan_instance.cc index b324f86f2..7505f8bf0 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.cc +++ b/src/xenia/ui/vulkan/vulkan_instance.cc @@ -10,21 +10,28 @@ #include "xenia/ui/vulkan/vulkan_instance.h" #include +#include #include #include #include "third_party/renderdoc/renderdoc_app.h" -#include "third_party/volk/volk.h" #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/platform.h" #include "xenia/base/profiling.h" #include "xenia/ui/vulkan/vulkan.h" #include "xenia/ui/vulkan/vulkan_immediate_drawer.h" #include "xenia/ui/vulkan/vulkan_util.h" #include "xenia/ui/window.h" +#if XE_PLATFORM_LINUX +#include +#elif XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif + #if XE_PLATFORM_LINUX #include "xenia/ui/window_gtk.h" #endif @@ -71,8 +78,61 @@ VulkanInstance::~VulkanInstance() { DestroyInstance(); } bool VulkanInstance::Initialize() { auto version = Version::Parse(VK_API_VERSION); XELOGVK("Initializing Vulkan {}...", version.pretty_string); - if (volkInitialize() != VK_SUCCESS) { - XELOGE("volkInitialize() failed!"); + + // Load the library. + bool library_functions_loaded = true; +#if XE_PLATFORM_LINUX +#if XE_PLATFORM_ANDROID + const char* libvulkan_name = "libvulkan.so"; +#else + const char* libvulkan_name = "libvulkan.so.1"; +#endif + // http://developer.download.nvidia.com/mobile/shield/assets/Vulkan/UsingtheVulkanAPI.pdf + library_ = dlopen(libvulkan_name, RTLD_NOW | RTLD_LOCAL); + if (!library_) { + XELOGE("Failed to load {}", libvulkan_name); + return false; + } +#define XE_VULKAN_LOAD_MODULE_LFN(name) \ + library_functions_loaded &= \ + (lfn_.name = PFN_##name(dlsym(library_, #name))) != nullptr; +#elif XE_PLATFORM_WIN32 + library_ = LoadLibraryA("vulkan-1.dll"); + if (!library_) { + XELOGE("Failed to load vulkan-1.dll"); + return false; + } +#define XE_VULKAN_LOAD_MODULE_LFN(name) \ + library_functions_loaded &= \ + (lfn_.name = PFN_##name(GetProcAddress(library_, #name))) != nullptr; +#else +#error No Vulkan library loading provided for the target platform. +#endif + XE_VULKAN_LOAD_MODULE_LFN(vkGetInstanceProcAddr); + XE_VULKAN_LOAD_MODULE_LFN(vkDestroyInstance); +#undef XE_VULKAN_LOAD_MODULE_LFN + if (!library_functions_loaded) { + XELOGE("Failed to get Vulkan library function pointers"); + return false; + } + library_functions_loaded &= + (lfn_.vkCreateInstance = PFN_vkCreateInstance(lfn_.vkGetInstanceProcAddr( + VK_NULL_HANDLE, "vkCreateInstance"))) != nullptr; + library_functions_loaded &= + (lfn_.vkEnumerateInstanceExtensionProperties = + PFN_vkEnumerateInstanceExtensionProperties( + lfn_.vkGetInstanceProcAddr( + VK_NULL_HANDLE, + "vkEnumerateInstanceExtensionProperties"))) != nullptr; + library_functions_loaded &= + (lfn_.vkEnumerateInstanceLayerProperties = + PFN_vkEnumerateInstanceLayerProperties(lfn_.vkGetInstanceProcAddr( + VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"))) != + nullptr; + if (!library_functions_loaded) { + XELOGE( + "Failed to get Vulkan library function pointers via " + "vkGetInstanceProcAddr"); return false; } @@ -149,11 +209,11 @@ bool VulkanInstance::QueryGlobals() { std::vector global_layer_properties; VkResult err; do { - err = vkEnumerateInstanceLayerProperties(&count, nullptr); + err = lfn_.vkEnumerateInstanceLayerProperties(&count, nullptr); CheckResult(err, "vkEnumerateInstanceLayerProperties"); global_layer_properties.resize(count); - err = vkEnumerateInstanceLayerProperties(&count, - global_layer_properties.data()); + err = lfn_.vkEnumerateInstanceLayerProperties( + &count, global_layer_properties.data()); } while (err == VK_INCOMPLETE); CheckResult(err, "vkEnumerateInstanceLayerProperties"); global_layers_.resize(count); @@ -163,11 +223,11 @@ bool VulkanInstance::QueryGlobals() { // Get all extensions available for the layer. do { - err = vkEnumerateInstanceExtensionProperties( + err = lfn_.vkEnumerateInstanceExtensionProperties( global_layer.properties.layerName, &count, nullptr); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); global_layer.extensions.resize(count); - err = vkEnumerateInstanceExtensionProperties( + err = lfn_.vkEnumerateInstanceExtensionProperties( global_layer.properties.layerName, &count, global_layer.extensions.data()); } while (err == VK_INCOMPLETE); @@ -190,11 +250,11 @@ bool VulkanInstance::QueryGlobals() { // Scan global extensions. do { - err = vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + err = lfn_.vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); global_extensions_.resize(count); - err = vkEnumerateInstanceExtensionProperties(nullptr, &count, - global_extensions_.data()); + err = lfn_.vkEnumerateInstanceExtensionProperties( + nullptr, &count, global_extensions_.data()); } while (err == VK_INCOMPLETE); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); XELOGVK("Found {} global extensions:", global_extensions_.size()); @@ -246,7 +306,7 @@ bool VulkanInstance::CreateInstance() { static_cast(enabled_extensions.size()); instance_info.ppEnabledExtensionNames = enabled_extensions.data(); - auto err = vkCreateInstance(&instance_info, nullptr, &handle); + auto err = lfn_.vkCreateInstance(&instance_info, nullptr, &handle); if (err != VK_SUCCESS) { XELOGE("vkCreateInstance returned {}", to_string(err)); } @@ -273,8 +333,39 @@ bool VulkanInstance::CreateInstance() { return false; } - // Load Vulkan entrypoints and extensions. - volkLoadInstance(handle); + // Check if extensions are enabled. + dbg_report_ena_ = false; + for (const char* enabled_extension : enabled_extensions) { + if (!dbg_report_ena_ && + !std::strcmp(enabled_extension, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { + dbg_report_ena_ = true; + } + } + + // Get instance functions. + std::memset(&ifn_, 0, sizeof(ifn_)); + bool instance_functions_loaded = true; +#define XE_UI_VULKAN_FUNCTION(name) \ + instance_functions_loaded &= \ + (ifn_.name = PFN_##name(lfn_.vkGetInstanceProcAddr(handle, #name))) != \ + nullptr; +#include "xenia/ui/vulkan/functions/instance_1_0.inc" +#include "xenia/ui/vulkan/functions/instance_khr_surface.inc" +#if XE_PLATFORM_ANDROID +#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc" +#elif XE_PLATFORM_GNULINUX +#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc" +#elif XE_PLATFORM_WIN32 +#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc" +#endif + if (dbg_report_ena_) { +#include "xenia/ui/vulkan/functions/instance_ext_debug_report.inc" + } +#undef XE_VULKAN_LOAD_IFN + if (!instance_functions_loaded) { + XELOGE("Failed to get Vulkan instance function pointers"); + return false; + } // Enable debug validation, if needed. EnableDebugValidation(); @@ -283,12 +374,23 @@ bool VulkanInstance::CreateInstance() { } void VulkanInstance::DestroyInstance() { - if (!handle) { - return; + if (handle) { + DisableDebugValidation(); + lfn_.vkDestroyInstance(handle, nullptr); + handle = nullptr; } - DisableDebugValidation(); - vkDestroyInstance(handle, nullptr); - handle = nullptr; + +#if XE_PLATFORM_LINUX + if (library_) { + dlclose(library_); + library_ = nullptr; + } +#elif XE_PLATFORM_WIN32 + if (library_) { + FreeLibrary(library_); + library_ = nullptr; + } +#endif } VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags, @@ -329,16 +431,13 @@ VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags, } void VulkanInstance::EnableDebugValidation() { - if (dbg_report_callback_) { - DisableDebugValidation(); - } - auto vk_create_debug_report_callback_ext = - reinterpret_cast( - vkGetInstanceProcAddr(handle, "vkCreateDebugReportCallbackEXT")); - if (!vk_create_debug_report_callback_ext) { + if (!dbg_report_ena_) { XELOGVK("Debug validation layer not installed; ignoring"); return; } + if (dbg_report_callback_) { + DisableDebugValidation(); + } VkDebugReportCallbackCreateInfoEXT create_info; create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; create_info.pNext = nullptr; @@ -349,7 +448,7 @@ void VulkanInstance::EnableDebugValidation() { VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT; create_info.pfnCallback = &DebugMessageCallback; create_info.pUserData = this; - auto status = vk_create_debug_report_callback_ext( + auto status = ifn_.vkCreateDebugReportCallbackEXT( handle, &create_info, nullptr, &dbg_report_callback_); if (status == VK_SUCCESS) { XELOGVK("Debug validation layer enabled"); @@ -360,16 +459,10 @@ void VulkanInstance::EnableDebugValidation() { } void VulkanInstance::DisableDebugValidation() { - if (!dbg_report_callback_) { + if (!dbg_report_ena_ || !dbg_report_callback_) { return; } - auto vk_destroy_debug_report_callback_ext = - reinterpret_cast( - vkGetInstanceProcAddr(handle, "vkDestroyDebugReportCallbackEXT")); - if (!vk_destroy_debug_report_callback_ext) { - return; - } - vk_destroy_debug_report_callback_ext(handle, dbg_report_callback_, nullptr); + ifn_.vkDestroyDebugReportCallbackEXT(handle, dbg_report_callback_, nullptr); dbg_report_callback_ = nullptr; } @@ -377,11 +470,11 @@ bool VulkanInstance::QueryDevices() { // Get handles to all devices. uint32_t count = 0; std::vector device_handles; - auto err = vkEnumeratePhysicalDevices(handle, &count, nullptr); + auto err = ifn_.vkEnumeratePhysicalDevices(handle, &count, nullptr); CheckResult(err, "vkEnumeratePhysicalDevices"); device_handles.resize(count); - err = vkEnumeratePhysicalDevices(handle, &count, device_handles.data()); + err = ifn_.vkEnumeratePhysicalDevices(handle, &count, device_handles.data()); CheckResult(err, "vkEnumeratePhysicalDevices"); // Query device info. @@ -391,33 +484,34 @@ bool VulkanInstance::QueryDevices() { device_info.handle = device_handle; // Query general attributes. - vkGetPhysicalDeviceProperties(device_handle, &device_info.properties); - vkGetPhysicalDeviceFeatures(device_handle, &device_info.features); - vkGetPhysicalDeviceMemoryProperties(device_handle, - &device_info.memory_properties); + ifn_.vkGetPhysicalDeviceProperties(device_handle, &device_info.properties); + ifn_.vkGetPhysicalDeviceFeatures(device_handle, &device_info.features); + ifn_.vkGetPhysicalDeviceMemoryProperties(device_handle, + &device_info.memory_properties); // Gather queue family properties. - vkGetPhysicalDeviceQueueFamilyProperties(device_handle, &count, nullptr); + ifn_.vkGetPhysicalDeviceQueueFamilyProperties(device_handle, &count, + nullptr); device_info.queue_family_properties.resize(count); - vkGetPhysicalDeviceQueueFamilyProperties( + ifn_.vkGetPhysicalDeviceQueueFamilyProperties( device_handle, &count, device_info.queue_family_properties.data()); // Gather layers. std::vector layer_properties; - err = vkEnumerateDeviceLayerProperties(device_handle, &count, nullptr); + err = ifn_.vkEnumerateDeviceLayerProperties(device_handle, &count, nullptr); CheckResult(err, "vkEnumerateDeviceLayerProperties"); layer_properties.resize(count); - err = vkEnumerateDeviceLayerProperties(device_handle, &count, - layer_properties.data()); + err = ifn_.vkEnumerateDeviceLayerProperties(device_handle, &count, + layer_properties.data()); CheckResult(err, "vkEnumerateDeviceLayerProperties"); for (size_t j = 0; j < layer_properties.size(); ++j) { LayerInfo layer_info; layer_info.properties = layer_properties[j]; - err = vkEnumerateDeviceExtensionProperties( + err = ifn_.vkEnumerateDeviceExtensionProperties( device_handle, layer_info.properties.layerName, &count, nullptr); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); layer_info.extensions.resize(count); - err = vkEnumerateDeviceExtensionProperties( + err = ifn_.vkEnumerateDeviceExtensionProperties( device_handle, layer_info.properties.layerName, &count, layer_info.extensions.data()); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); @@ -425,12 +519,12 @@ bool VulkanInstance::QueryDevices() { } // Gather extensions. - err = vkEnumerateDeviceExtensionProperties(device_handle, nullptr, &count, - nullptr); + err = ifn_.vkEnumerateDeviceExtensionProperties(device_handle, nullptr, + &count, nullptr); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); device_info.extensions.resize(count); - err = vkEnumerateDeviceExtensionProperties(device_handle, nullptr, &count, - device_info.extensions.data()); + err = ifn_.vkEnumerateDeviceExtensionProperties( + device_handle, nullptr, &count, device_info.extensions.data()); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); available_devices_.push_back(std::move(device_info)); diff --git a/src/xenia/ui/vulkan/vulkan_instance.h b/src/xenia/ui/vulkan/vulkan_instance.h index 6a86933bc..8c650a2e3 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.h +++ b/src/xenia/ui/vulkan/vulkan_instance.h @@ -14,6 +14,7 @@ #include #include +#include "xenia/base/platform.h" #include "xenia/ui/vulkan/vulkan.h" #include "xenia/ui/vulkan/vulkan_util.h" #include "xenia/ui/window.h" @@ -28,10 +29,38 @@ class VulkanInstance { VulkanInstance(); ~VulkanInstance(); + struct LibraryFunctions { + // From the module. + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkDestroyInstance vkDestroyInstance; + // From vkGetInstanceProcAddr. + PFN_vkCreateInstance vkCreateInstance; + PFN_vkEnumerateInstanceExtensionProperties + vkEnumerateInstanceExtensionProperties; + PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties; + }; + const LibraryFunctions& lfn() const { return lfn_; } + VkInstance handle = nullptr; operator VkInstance() const { return handle; } + struct InstanceFunctions { +#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name; +#include "xenia/ui/vulkan/functions/instance_1_0.inc" +#include "xenia/ui/vulkan/functions/instance_ext_debug_report.inc" +#include "xenia/ui/vulkan/functions/instance_khr_surface.inc" +#if XE_PLATFORM_ANDROID +#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc" +#elif XE_PLATFORM_GNULINUX +#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc" +#elif XE_PLATFORM_WIN32 +#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc" +#endif +#undef XE_UI_VULKAN_FUNCTION + }; + const InstanceFunctions& ifn() const { return ifn_; } + // Declares a layer to verify and enable upon initialization. // Must be called before Initialize. void DeclareRequiredLayer(std::string name, uint32_t min_version, @@ -85,13 +114,24 @@ class VulkanInstance { const char* indent); void DumpDeviceInfo(const DeviceInfo& device_info); +#if XE_PLATFORM_LINUX + void* library_ = nullptr; +#elif XE_PLATFORM_WIN32 + HMODULE library_ = nullptr; +#endif + + LibraryFunctions lfn_ = {}; + std::vector required_layers_; std::vector required_extensions_; + InstanceFunctions ifn_ = {}; + std::vector global_layers_; std::vector global_extensions_; std::vector available_devices_; + bool dbg_report_ena_ = false; VkDebugReportCallbackEXT dbg_report_callback_ = nullptr; void* renderdoc_api_ = nullptr; diff --git a/src/xenia/ui/vulkan/vulkan_mem_alloc.h b/src/xenia/ui/vulkan/vulkan_mem_alloc.h index caf89aae6..31a9b2c40 100644 --- a/src/xenia/ui/vulkan/vulkan_mem_alloc.h +++ b/src/xenia/ui/vulkan/vulkan_mem_alloc.h @@ -10,31 +10,35 @@ #ifndef XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_ #define XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_ -#include "third_party/volk/volk.h" - #define VMA_STATIC_VULKAN_FUNCTIONS 0 #include "third_party/vulkan/vk_mem_alloc.h" +#include "xenia/ui/vulkan/vulkan_device.h" +#include "xenia/ui/vulkan/vulkan_instance.h" + namespace xe { namespace ui { namespace vulkan { -inline void FillVMAVulkanFunctions(VmaVulkanFunctions* vma_funcs) { - vma_funcs->vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties; +inline void FillVMAVulkanFunctions(VmaVulkanFunctions* vma_funcs, + const VulkanDevice& device) { + const VulkanInstance::InstanceFunctions& ifn = device.instance()->ifn(); + const VulkanDevice::DeviceFunctions& dfn = device.dfn(); + vma_funcs->vkGetPhysicalDeviceProperties = ifn.vkGetPhysicalDeviceProperties; vma_funcs->vkGetPhysicalDeviceMemoryProperties = - vkGetPhysicalDeviceMemoryProperties; - vma_funcs->vkAllocateMemory = vkAllocateMemory; - vma_funcs->vkFreeMemory = vkFreeMemory; - vma_funcs->vkMapMemory = vkMapMemory; - vma_funcs->vkUnmapMemory = vkUnmapMemory; - vma_funcs->vkBindBufferMemory = vkBindBufferMemory; - vma_funcs->vkBindImageMemory = vkBindImageMemory; - vma_funcs->vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements; - vma_funcs->vkGetImageMemoryRequirements = vkGetImageMemoryRequirements; - vma_funcs->vkCreateBuffer = vkCreateBuffer; - vma_funcs->vkDestroyBuffer = vkDestroyBuffer; - vma_funcs->vkCreateImage = vkCreateImage; - vma_funcs->vkDestroyImage = vkDestroyImage; + ifn.vkGetPhysicalDeviceMemoryProperties; + vma_funcs->vkAllocateMemory = dfn.vkAllocateMemory; + vma_funcs->vkFreeMemory = dfn.vkFreeMemory; + vma_funcs->vkMapMemory = dfn.vkMapMemory; + vma_funcs->vkUnmapMemory = dfn.vkUnmapMemory; + vma_funcs->vkBindBufferMemory = dfn.vkBindBufferMemory; + vma_funcs->vkBindImageMemory = dfn.vkBindImageMemory; + vma_funcs->vkGetBufferMemoryRequirements = dfn.vkGetBufferMemoryRequirements; + vma_funcs->vkGetImageMemoryRequirements = dfn.vkGetImageMemoryRequirements; + vma_funcs->vkCreateBuffer = dfn.vkCreateBuffer; + vma_funcs->vkDestroyBuffer = dfn.vkDestroyBuffer; + vma_funcs->vkCreateImage = dfn.vkCreateImage; + vma_funcs->vkDestroyImage = dfn.vkDestroyImage; } } // namespace vulkan diff --git a/src/xenia/ui/vulkan/vulkan_swap_chain.cc b/src/xenia/ui/vulkan/vulkan_swap_chain.cc index ae91e493b..79f74a782 100644 --- a/src/xenia/ui/vulkan/vulkan_swap_chain.cc +++ b/src/xenia/ui/vulkan/vulkan_swap_chain.cc @@ -35,6 +35,8 @@ VulkanSwapChain::~VulkanSwapChain() { Shutdown(); } VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { surface_ = surface; + const VulkanInstance::InstanceFunctions& ifn = instance_->ifn(); + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Find a queue family that supports presentation. @@ -49,8 +51,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { continue; } - status = vkGetPhysicalDeviceSurfaceSupportKHR(*device_, i, surface, - &surface_supported); + status = ifn.vkGetPhysicalDeviceSurfaceSupportKHR(*device_, i, surface, + &surface_supported); if (status == VK_SUCCESS && surface_supported == VK_TRUE) { queue_family_index = i; break; @@ -80,13 +82,13 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { // Query supported target formats. uint32_t count = 0; - status = - vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, nullptr); + status = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, + nullptr); CheckResult(status, "vkGetPhysicalDeviceSurfaceFormatsKHR"); std::vector surface_formats; surface_formats.resize(count); - status = vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, - surface_formats.data()); + status = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(*device_, surface_, &count, + surface_formats.data()); CheckResult(status, "vkGetPhysicalDeviceSurfaceFormatsKHR"); if (status != VK_SUCCESS) { return status; @@ -107,8 +109,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { // Query surface min/max/caps. VkSurfaceCapabilitiesKHR surface_caps; - status = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(*device_, surface_, - &surface_caps); + status = ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(*device_, surface_, + &surface_caps); CheckResult(status, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); if (status != VK_SUCCESS) { return status; @@ -116,16 +118,16 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { // Query surface properties so we can configure ourselves within bounds. std::vector present_modes; - status = vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_, &count, - nullptr); + status = ifn.vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_, + &count, nullptr); CheckResult(status, "vkGetPhysicalDeviceSurfacePresentModesKHR"); if (status != VK_SUCCESS) { return status; } present_modes.resize(count); - status = vkGetPhysicalDeviceSurfacePresentModesKHR(*device_, surface_, &count, - present_modes.data()); + status = ifn.vkGetPhysicalDeviceSurfacePresentModesKHR( + *device_, surface_, &count, present_modes.data()); CheckResult(status, "vkGetPhysicalDeviceSurfacePresentModesKHR"); if (status != VK_SUCCESS) { return status; @@ -210,7 +212,7 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { XELOGVK(" imageSharingMode = {}", to_string(create_info.imageSharingMode)); XELOGVK(" queueFamilyCount = {}", create_info.queueFamilyIndexCount); - status = vkCreateSwapchainKHR(*device_, &create_info, nullptr, &handle); + status = dfn.vkCreateSwapchainKHR(*device_, &create_info, nullptr, &handle); if (status != VK_SUCCESS) { XELOGE("Failed to create swapchain: {}", to_string(status)); return status; @@ -223,7 +225,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { cmd_pool_info.pNext = nullptr; cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; cmd_pool_info.queueFamilyIndex = presentation_queue_family_; - status = vkCreateCommandPool(*device_, &cmd_pool_info, nullptr, &cmd_pool_); + status = + dfn.vkCreateCommandPool(*device_, &cmd_pool_info, nullptr, &cmd_pool_); CheckResult(status, "vkCreateCommandPool"); if (status != VK_SUCCESS) { return status; @@ -236,7 +239,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { cmd_buffer_info.commandPool = cmd_pool_; cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_buffer_info.commandBufferCount = 2; - status = vkAllocateCommandBuffers(*device_, &cmd_buffer_info, &cmd_buffer_); + status = + dfn.vkAllocateCommandBuffers(*device_, &cmd_buffer_info, &cmd_buffer_); CheckResult(status, "vkCreateCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -247,7 +251,7 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { cmd_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; cmd_buffer_info.commandBufferCount = 2; status = - vkAllocateCommandBuffers(*device_, &cmd_buffer_info, command_buffers); + dfn.vkAllocateCommandBuffers(*device_, &cmd_buffer_info, command_buffers); CheckResult(status, "vkCreateCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -296,8 +300,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { render_pass_info.pSubpasses = &render_subpass; render_pass_info.dependencyCount = 0; render_pass_info.pDependencies = nullptr; - status = - vkCreateRenderPass(*device_, &render_pass_info, nullptr, &render_pass_); + status = dfn.vkCreateRenderPass(*device_, &render_pass_info, nullptr, + &render_pass_); CheckResult(status, "vkCreateRenderPass"); if (status != VK_SUCCESS) { return status; @@ -308,16 +312,16 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; semaphore_info.pNext = nullptr; semaphore_info.flags = 0; - status = vkCreateSemaphore(*device_, &semaphore_info, nullptr, - &image_available_semaphore_); + status = dfn.vkCreateSemaphore(*device_, &semaphore_info, nullptr, + &image_available_semaphore_); CheckResult(status, "vkCreateSemaphore"); if (status != VK_SUCCESS) { return status; } // Create another semaphore used to synchronize writes to the swap image. - status = vkCreateSemaphore(*device_, &semaphore_info, nullptr, - &image_usage_semaphore_); + status = dfn.vkCreateSemaphore(*device_, &semaphore_info, nullptr, + &image_usage_semaphore_); CheckResult(status, "vkCreateSemaphore"); if (status != VK_SUCCESS) { return status; @@ -327,16 +331,16 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { // Note that this may differ from our requested amount. uint32_t actual_image_count = 0; std::vector images; - status = - vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, nullptr); + status = dfn.vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, + nullptr); CheckResult(status, "vkGetSwapchainImagesKHR"); if (status != VK_SUCCESS) { return status; } images.resize(actual_image_count); - status = vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, - images.data()); + status = dfn.vkGetSwapchainImagesKHR(*device_, handle, &actual_image_count, + images.data()); CheckResult(status, "vkGetSwapchainImagesKHR"); if (status != VK_SUCCESS) { return status; @@ -360,8 +364,8 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) { nullptr, VK_FENCE_CREATE_SIGNALED_BIT, }; - status = vkCreateFence(*device_, &fence_create_info, nullptr, - &synchronization_fence_); + status = dfn.vkCreateFence(*device_, &fence_create_info, nullptr, + &synchronization_fence_); CheckResult(status, "vkGetSwapchainImagesKHR"); if (status != VK_SUCCESS) { return status; @@ -376,6 +380,7 @@ VkResult VulkanSwapChain::InitializeBuffer(Buffer* buffer, DestroyBuffer(buffer); buffer->image = target_image; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Create an image view for the presentation image. @@ -396,8 +401,8 @@ VkResult VulkanSwapChain::InitializeBuffer(Buffer* buffer, image_view_info.subresourceRange.levelCount = 1; image_view_info.subresourceRange.baseArrayLayer = 0; image_view_info.subresourceRange.layerCount = 1; - status = vkCreateImageView(*device_, &image_view_info, nullptr, - &buffer->image_view); + status = dfn.vkCreateImageView(*device_, &image_view_info, nullptr, + &buffer->image_view); CheckResult(status, "vkCreateImageView"); if (status != VK_SUCCESS) { return status; @@ -416,8 +421,8 @@ VkResult VulkanSwapChain::InitializeBuffer(Buffer* buffer, framebuffer_info.width = surface_width_; framebuffer_info.height = surface_height_; framebuffer_info.layers = 1; - status = vkCreateFramebuffer(*device_, &framebuffer_info, nullptr, - &buffer->framebuffer); + status = dfn.vkCreateFramebuffer(*device_, &framebuffer_info, nullptr, + &buffer->framebuffer); CheckResult(status, "vkCreateFramebuffer"); if (status != VK_SUCCESS) { return status; @@ -427,12 +432,13 @@ VkResult VulkanSwapChain::InitializeBuffer(Buffer* buffer, } void VulkanSwapChain::DestroyBuffer(Buffer* buffer) { + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); if (buffer->framebuffer) { - vkDestroyFramebuffer(*device_, buffer->framebuffer, nullptr); + dfn.vkDestroyFramebuffer(*device_, buffer->framebuffer, nullptr); buffer->framebuffer = nullptr; } if (buffer->image_view) { - vkDestroyImageView(*device_, buffer->image_view, nullptr); + dfn.vkDestroyImageView(*device_, buffer->image_view, nullptr); buffer->image_view = nullptr; } // Image is taken care of by the presentation engine. @@ -458,19 +464,21 @@ void VulkanSwapChain::Shutdown() { } buffers_.clear(); - VK_SAFE_DESTROY(vkDestroySemaphore, *device_, image_available_semaphore_, - nullptr); - VK_SAFE_DESTROY(vkDestroyRenderPass, *device_, render_pass_, nullptr); + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); + + DestroyAndNullHandle(dfn.vkDestroySemaphore, *device_, + image_available_semaphore_); + DestroyAndNullHandle(dfn.vkDestroyRenderPass, *device_, render_pass_); if (copy_cmd_buffer_) { - vkFreeCommandBuffers(*device_, cmd_pool_, 1, ©_cmd_buffer_); + dfn.vkFreeCommandBuffers(*device_, cmd_pool_, 1, ©_cmd_buffer_); copy_cmd_buffer_ = nullptr; } if (render_cmd_buffer_) { - vkFreeCommandBuffers(*device_, cmd_pool_, 1, &render_cmd_buffer_); + dfn.vkFreeCommandBuffers(*device_, cmd_pool_, 1, &render_cmd_buffer_); render_cmd_buffer_ = nullptr; } - VK_SAFE_DESTROY(vkDestroyCommandPool, *device_, cmd_pool_, nullptr); + DestroyAndNullHandle(dfn.vkDestroyCommandPool, *device_, cmd_pool_); if (presentation_queue_) { if (!presentation_queue_mutex_) { @@ -482,33 +490,36 @@ void VulkanSwapChain::Shutdown() { presentation_queue_family_ = -1; } - VK_SAFE_DESTROY(vkDestroyFence, *device_, synchronization_fence_, nullptr); + DestroyAndNullHandle(dfn.vkDestroyFence, *device_, synchronization_fence_); // images_ doesn't need to be cleaned up as the swapchain does it implicitly. - VK_SAFE_DESTROY(vkDestroySwapchainKHR, *device_, handle, nullptr); - VK_SAFE_DESTROY(vkDestroySurfaceKHR, *instance_, surface_, nullptr); + DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, *device_, handle); + const VulkanInstance::InstanceFunctions& ifn = instance_->ifn(); + DestroyAndNullHandle(ifn.vkDestroySurfaceKHR, *instance_, surface_); } VkResult VulkanSwapChain::Begin() { wait_semaphores_.clear(); + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; // Wait for the last swap to finish. - status = vkWaitForFences(*device_, 1, &synchronization_fence_, VK_TRUE, -1); + status = + dfn.vkWaitForFences(*device_, 1, &synchronization_fence_, VK_TRUE, -1); if (status != VK_SUCCESS) { return status; } - status = vkResetFences(*device_, 1, &synchronization_fence_); + status = dfn.vkResetFences(*device_, 1, &synchronization_fence_); if (status != VK_SUCCESS) { return status; } // Get the index of the next available swapchain image. status = - vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_, - nullptr, ¤t_buffer_index_); + dfn.vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_, + nullptr, ¤t_buffer_index_); if (status != VK_SUCCESS) { return status; } @@ -531,7 +542,8 @@ VkResult VulkanSwapChain::Begin() { if (presentation_queue_mutex_) { presentation_queue_mutex_->lock(); } - status = vkQueueSubmit(presentation_queue_, 1, &wait_submit_info, nullptr); + status = + dfn.vkQueueSubmit(presentation_queue_, 1, &wait_submit_info, nullptr); if (presentation_queue_mutex_) { presentation_queue_mutex_->unlock(); } @@ -540,8 +552,8 @@ VkResult VulkanSwapChain::Begin() { } // Reset all command buffers. - vkResetCommandBuffer(render_cmd_buffer_, 0); - vkResetCommandBuffer(copy_cmd_buffer_, 0); + dfn.vkResetCommandBuffer(render_cmd_buffer_, 0); + dfn.vkResetCommandBuffer(copy_cmd_buffer_, 0); auto& current_buffer = buffers_[current_buffer_index_]; // Build the command buffer that will execute all queued rendering buffers. @@ -561,7 +573,7 @@ VkResult VulkanSwapChain::Begin() { begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = &inherit_info; - status = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); + status = dfn.vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); CheckResult(status, "vkBeginCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -569,7 +581,7 @@ VkResult VulkanSwapChain::Begin() { // Start recording the copy command buffer as well. begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - status = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); + status = dfn.vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); CheckResult(status, "vkBeginCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -588,31 +600,32 @@ VkResult VulkanSwapChain::Begin() { clear_color.float32[1] = 1.0f; clear_color.float32[2] = 0.0f; } - vkCmdClearColorImage(copy_cmd_buffer_, current_buffer.image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, - &clear_range); + dfn.vkCmdClearColorImage(copy_cmd_buffer_, current_buffer.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, + 1, &clear_range); return VK_SUCCESS; } VkResult VulkanSwapChain::End() { auto& current_buffer = buffers_[current_buffer_index_]; + const VulkanDevice::DeviceFunctions& dfn = device_->dfn(); VkResult status; - status = vkEndCommandBuffer(render_cmd_buffer_); + status = dfn.vkEndCommandBuffer(render_cmd_buffer_); CheckResult(status, "vkEndCommandBuffer"); if (status != VK_SUCCESS) { return status; } - status = vkEndCommandBuffer(copy_cmd_buffer_); + status = dfn.vkEndCommandBuffer(copy_cmd_buffer_); CheckResult(status, "vkEndCommandBuffer"); if (status != VK_SUCCESS) { return status; } // Build primary command buffer. - status = vkResetCommandBuffer(cmd_buffer_, 0); + status = dfn.vkResetCommandBuffer(cmd_buffer_, 0); CheckResult(status, "vkResetCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -623,7 +636,7 @@ VkResult VulkanSwapChain::End() { begin_info.pNext = nullptr; begin_info.flags = 0; begin_info.pInheritanceInfo = nullptr; - status = vkBeginCommandBuffer(cmd_buffer_, &begin_info); + status = dfn.vkBeginCommandBuffer(cmd_buffer_, &begin_info); CheckResult(status, "vkBeginCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -642,14 +655,14 @@ VkResult VulkanSwapChain::End() { pre_image_copy_barrier.image = current_buffer.image; pre_image_copy_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, - nullptr, 1, &pre_image_copy_barrier); + dfn.vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &pre_image_copy_barrier); current_buffer.image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; // Execute copy commands - vkCmdExecuteCommands(cmd_buffer_, 1, ©_cmd_buffer_); + dfn.vkCmdExecuteCommands(cmd_buffer_, 1, ©_cmd_buffer_); // Transition the image to a color attachment target for drawing. VkImageMemoryBarrier pre_image_memory_barrier; @@ -669,9 +682,9 @@ VkResult VulkanSwapChain::End() { pre_image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; pre_image_memory_barrier.oldLayout = current_buffer.image_layout; pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, - nullptr, 0, nullptr, 1, &pre_image_memory_barrier); + dfn.vkCmdPipelineBarrier(cmd_buffer_, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, + nullptr, 0, nullptr, 1, &pre_image_memory_barrier); current_buffer.image_layout = pre_image_memory_barrier.newLayout; @@ -687,14 +700,14 @@ VkResult VulkanSwapChain::End() { render_pass_begin_info.renderArea.extent.height = surface_height_; render_pass_begin_info.clearValueCount = 0; render_pass_begin_info.pClearValues = nullptr; - vkCmdBeginRenderPass(cmd_buffer_, &render_pass_begin_info, - VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); + dfn.vkCmdBeginRenderPass(cmd_buffer_, &render_pass_begin_info, + VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); // Render commands. - vkCmdExecuteCommands(cmd_buffer_, 1, &render_cmd_buffer_); + dfn.vkCmdExecuteCommands(cmd_buffer_, 1, &render_cmd_buffer_); // End render pass. - vkCmdEndRenderPass(cmd_buffer_); + dfn.vkCmdEndRenderPass(cmd_buffer_); // Transition the image to a format the presentation engine can source from. // FIXME: Do we need more synchronization here between the copy buffer? @@ -715,14 +728,14 @@ VkResult VulkanSwapChain::End() { post_image_memory_barrier.subresourceRange.levelCount = 1; post_image_memory_barrier.subresourceRange.baseArrayLayer = 0; post_image_memory_barrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(cmd_buffer_, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &post_image_memory_barrier); + dfn.vkCmdPipelineBarrier(cmd_buffer_, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &post_image_memory_barrier); current_buffer.image_layout = post_image_memory_barrier.newLayout; - status = vkEndCommandBuffer(cmd_buffer_); + status = dfn.vkEndCommandBuffer(cmd_buffer_); CheckResult(status, "vkEndCommandBuffer"); if (status != VK_SUCCESS) { return status; @@ -752,8 +765,8 @@ VkResult VulkanSwapChain::End() { if (presentation_queue_mutex_) { presentation_queue_mutex_->lock(); } - status = vkQueueSubmit(presentation_queue_, 1, &render_submit_info, - synchronization_fence_); + status = dfn.vkQueueSubmit(presentation_queue_, 1, &render_submit_info, + synchronization_fence_); if (presentation_queue_mutex_) { presentation_queue_mutex_->unlock(); } @@ -777,7 +790,7 @@ VkResult VulkanSwapChain::End() { if (presentation_queue_mutex_) { presentation_queue_mutex_->lock(); } - status = vkQueuePresentKHR(presentation_queue_, &present_info); + status = dfn.vkQueuePresentKHR(presentation_queue_, &present_info); if (presentation_queue_mutex_) { presentation_queue_mutex_->unlock(); } diff --git a/src/xenia/ui/vulkan/vulkan_util.h b/src/xenia/ui/vulkan/vulkan_util.h index ac47e5d4b..d57c6c0fa 100644 --- a/src/xenia/ui/vulkan/vulkan_util.h +++ b/src/xenia/ui/vulkan/vulkan_util.h @@ -26,39 +26,37 @@ namespace xe { namespace ui { namespace vulkan { -#define VK_SAFE_DESTROY(fn, dev, obj, alloc) \ - \ - do { \ - if (obj) { \ - fn(dev, obj, alloc); \ - obj = nullptr; \ - } \ - \ - } while (0) - -class Fence { - public: - Fence(VkDevice device) : device_(device) { - VkFenceCreateInfo fence_info; - fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fence_info.pNext = nullptr; - fence_info.flags = 0; - vkCreateFence(device, &fence_info, nullptr, &fence_); +template +inline bool DestroyAndNullHandle(F* destroy_function, T& handle) { + if (handle != VK_NULL_HANDLE) { + destroy_function(handle, nullptr); + handle = VK_NULL_HANDLE; + return true; } - ~Fence() { - vkDestroyFence(device_, fence_, nullptr); - fence_ = nullptr; + return false; +} + +template +inline bool DestroyAndNullHandle(F* destroy_function, VkInstance parent, + T& handle) { + if (handle != VK_NULL_HANDLE) { + destroy_function(parent, handle, nullptr); + handle = VK_NULL_HANDLE; + return true; } + return false; +} - VkResult status() const { return vkGetFenceStatus(device_, fence_); } - - VkFence fence() const { return fence_; } - operator VkFence() const { return fence_; } - - private: - VkDevice device_; - VkFence fence_ = nullptr; -}; +template +inline bool DestroyAndNullHandle(F* destroy_function, VkDevice parent, + T& handle) { + if (handle != VK_NULL_HANDLE) { + destroy_function(parent, handle, nullptr); + handle = VK_NULL_HANDLE; + return true; + } + return false; +} struct Version { uint32_t major; diff --git a/third_party/volk b/third_party/volk deleted file mode 160000 index 30a851b67..000000000 --- a/third_party/volk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 30a851b67e129a3d91f191b2e9dcdad65ba98438 diff --git a/third_party/volk.lua b/third_party/volk.lua deleted file mode 100644 index 7ba0dd618..000000000 --- a/third_party/volk.lua +++ /dev/null @@ -1,30 +0,0 @@ -group("third_party") -project("volk") - uuid("C9781C93-2DF5-47A2-94EE-2C5EBED61239") - kind("StaticLib") - language("C") - - defines({ - "_LIB", - "API_NAME=\"vulkan\"", - }) - removedefines({ - "_UNICODE", - "UNICODE", - }) - includedirs({ - "volk", - }) - files({ - "volk/volk.c", - "volk/volk.h", - }) - - filter("platforms:Windows") - defines({ - "VK_USE_PLATFORM_WIN32_KHR", - }) - filter("platforms:Linux") - defines({ - "VK_USE_PLATFORM_XCB_KHR", - }) From 6412bb8910fa140dedf57778149c6563fea5f511 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Mon, 12 Jul 2021 00:00:06 +0300 Subject: [PATCH 11/36] [Vulkan] Remove a remaining Volk reference --- src/xenia/gpu/vulkan/texture_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/gpu/vulkan/texture_config.h b/src/xenia/gpu/vulkan/texture_config.h index 5e23d19bd..6587b68cc 100644 --- a/src/xenia/gpu/vulkan/texture_config.h +++ b/src/xenia/gpu/vulkan/texture_config.h @@ -10,9 +10,9 @@ #ifndef XENIA_GPU_VULKAN_TEXTURE_CONFIG_H_ #define XENIA_GPU_VULKAN_TEXTURE_CONFIG_H_ -#include "third_party/volk/volk.h" #include "xenia/gpu/texture_info.h" #include "xenia/gpu/xenos.h" +#include "xenia/ui/vulkan/vulkan.h" namespace xe { namespace gpu { From 1e0237d404529a82fe59ee01e2a824b992d2d2d0 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Mon, 12 Jul 2021 12:15:47 +0300 Subject: [PATCH 12/36] [Vulkan] Fix XCB #ifdef --- src/xenia/ui/vulkan/vulkan_instance.cc | 2 +- src/xenia/ui/vulkan/vulkan_instance.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_instance.cc b/src/xenia/ui/vulkan/vulkan_instance.cc index 7505f8bf0..36064528e 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.cc +++ b/src/xenia/ui/vulkan/vulkan_instance.cc @@ -353,7 +353,7 @@ bool VulkanInstance::CreateInstance() { #include "xenia/ui/vulkan/functions/instance_khr_surface.inc" #if XE_PLATFORM_ANDROID #include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc" -#elif XE_PLATFORM_GNULINUX +#elif XE_PLATFORM_GNU_LINUX #include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc" #elif XE_PLATFORM_WIN32 #include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc" diff --git a/src/xenia/ui/vulkan/vulkan_instance.h b/src/xenia/ui/vulkan/vulkan_instance.h index 8c650a2e3..52a92f53e 100644 --- a/src/xenia/ui/vulkan/vulkan_instance.h +++ b/src/xenia/ui/vulkan/vulkan_instance.h @@ -52,7 +52,7 @@ class VulkanInstance { #include "xenia/ui/vulkan/functions/instance_khr_surface.inc" #if XE_PLATFORM_ANDROID #include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc" -#elif XE_PLATFORM_GNULINUX +#elif XE_PLATFORM_GNU_LINUX #include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc" #elif XE_PLATFORM_WIN32 #include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc" From a7efdd9ed80f98b98212ea18a9ca6993126d8b29 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Tue, 20 Jul 2021 20:16:39 +0300 Subject: [PATCH 13/36] [Build] Run pkg_config only on Linux, not all non-Windows --- tools/build/scripts/pkg_config.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build/scripts/pkg_config.lua b/tools/build/scripts/pkg_config.lua index 9e565de57..2b3bb97f5 100644 --- a/tools/build/scripts/pkg_config.lua +++ b/tools/build/scripts/pkg_config.lua @@ -3,7 +3,7 @@ pkg_config = {} function pkg_config.cflags(lib) - if os.istarget("windows") then + if not os.istarget("linux") then return end buildoptions({ @@ -12,7 +12,7 @@ function pkg_config.cflags(lib) end function pkg_config.lflags(lib) - if os.istarget("windows") then + if not os.istarget("linux") then return end linkoptions({ From 48bb93a9cacccae7f975e174fda658abc6148bf2 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Wed, 21 Jul 2021 16:32:58 +0200 Subject: [PATCH 14/36] [Linux] Fix pkg-config trailing space causing premake to output an extra empty library -l Fixes #1868 --- tools/build/scripts/pkg_config.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/build/scripts/pkg_config.lua b/tools/build/scripts/pkg_config.lua index 2b3bb97f5..2042491b9 100644 --- a/tools/build/scripts/pkg_config.lua +++ b/tools/build/scripts/pkg_config.lua @@ -24,7 +24,9 @@ function pkg_config.lflags(lib) local output = ({os.outputof("pkg-config --libs-only-l "..lib)})[1] for k, flag in next, string.explode(output, " ") do -- remove "-l" - links(string.sub(flag, 3)) + if flag ~= "" then + links(string.sub(flag, 3)) + end end end From 90c4950503b11ce6d99aa8fdbdf9177e90495473 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Mon, 26 Jul 2021 10:11:14 +0300 Subject: [PATCH 15/36] [HID] Fix SDL GetKeystroke copy-paste regression --- src/xenia/hid/sdl/sdl_input_driver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index 914f31bb8..0e4e7fd11 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -344,7 +344,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags, if (!(butts_changed & fbutton)) { continue; } - ui::VirtualKey vk = kVkLookup.at(last.repeat_butt_idx); + ui::VirtualKey vk = kVkLookup.at(i); if (vk == ui::VirtualKey::kNone) { continue; } From c9073e101f38afe7b1444bae97eb8b78dcb151f9 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 30 Jul 2021 11:42:37 +0200 Subject: [PATCH 16/36] [XAM] Fix ContentCreate to pass copy of root_name. [XAM] Fix xeXamContentCreate to pass copy of root_name for deferred operation, as the pointer may no longer be valid when the callback is executed. --- src/xenia/kernel/xam/xam_content.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 690edd6ca..dea81d46b 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -139,9 +139,9 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, *disposition_ptr = 0; } - auto run = [content_manager, root_name, flags, content_data, disposition_ptr, - license_mask_ptr](uint32_t& extended_error, - uint32_t& length) -> X_RESULT { + auto run = [content_manager, root_name = root_name.value(), flags, + content_data, disposition_ptr, license_mask_ptr]( + uint32_t& extended_error, uint32_t& length) -> X_RESULT { X_RESULT result = X_ERROR_INVALID_PARAMETER; bool create = false; bool open = false; @@ -203,9 +203,9 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, } if (create) { - result = content_manager->CreateContent(root_name.value(), content_data); + result = content_manager->CreateContent(root_name, content_data); } else if (open) { - result = content_manager->OpenContent(root_name.value(), content_data); + result = content_manager->OpenContent(root_name, content_data); } if (license_mask_ptr && XSUCCEEDED(result)) { From f933d9c409eb24c82d9d6723d082047165219eb7 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 6 Aug 2021 09:04:58 +0200 Subject: [PATCH 17/36] [XAM] XamEnumerate: Set initial item_count value to 0 --- src/xenia/kernel/xam/xam_enum.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/xenia/kernel/xam/xam_enum.cc b/src/xenia/kernel/xam/xam_enum.cc index cd511c37a..396d08dd7 100644 --- a/src/xenia/kernel/xam/xam_enum.cc +++ b/src/xenia/kernel/xam/xam_enum.cc @@ -40,10 +40,9 @@ uint32_t xeXamEnumerate(uint32_t handle, uint32_t flags, lpvoid_t buffer_ptr, auto run = [e, buffer_ptr](uint32_t& extended_error, uint32_t& length) -> X_RESULT { X_RESULT result; - uint32_t item_count; + uint32_t item_count = 0; if (!buffer_ptr) { result = X_ERROR_INVALID_PARAMETER; - item_count = 0; } else { result = e->WriteItems(buffer_ptr.guest_address(), buffer_ptr.as(), &item_count); From 4861022158ff02bf51660dcf1dbb33eb1ad4cded Mon Sep 17 00:00:00 2001 From: sephiroth99 Date: Thu, 5 Aug 2021 00:05:17 -0400 Subject: [PATCH 18/36] [Base] Fix fpfs with GCC/Clang The fpfs function is using strtof to convert a string to floating point value, but the type may be a double. Using strtof in that case won't provide enough precision, so switch to using strtod. When the type is a float, the double will be down-converted to the correct value. --- src/xenia/base/string_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/base/string_util.h b/src/xenia/base/string_util.h index 59d6e90f6..3c73f9c65 100644 --- a/src/xenia/base/string_util.h +++ b/src/xenia/base/string_util.h @@ -221,7 +221,7 @@ inline T fpfs(const std::string_view value, bool force_hex) { } else { #if XE_COMPILER_CLANG || XE_COMPILER_GNUC auto temp = std::string(range); - result = std::strtof(temp.c_str(), nullptr); + result = std::strtod(temp.c_str(), nullptr); #else auto [p, error] = std::from_chars(range.data(), range.data() + range.size(), result, std::chars_format::general); From a9e35b443febcbd26a81be38b7e6e97488f627da Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Tue, 10 Aug 2021 20:56:44 +0200 Subject: [PATCH 19/36] [SDL2] Update to version 2.0.16 --- third_party/SDL2 | 2 +- third_party/SDL2-static.lua | 43 ++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/third_party/SDL2 b/third_party/SDL2 index d28437de3..ea9bece5e 160000 --- a/third_party/SDL2 +++ b/third_party/SDL2 @@ -1 +1 @@ -Subproject commit d28437de3c74d2514fc96d4e0da1dec4113e8929 +Subproject commit ea9bece5ed4e76b6636d941101923487a6378ca6 diff --git a/third_party/SDL2-static.lua b/third_party/SDL2-static.lua index d30f914b3..492c0d3d9 100644 --- a/third_party/SDL2-static.lua +++ b/third_party/SDL2-static.lua @@ -50,6 +50,7 @@ project("SDL2") "SDL2/include/SDL_log.h", "SDL2/include/SDL_main.h", "SDL2/include/SDL_messagebox.h", + "SDL2/include/SDL_metal.h", "SDL2/include/SDL_misc.h", "SDL2/include/SDL_mouse.h", "SDL2/include/SDL_mutex.h", @@ -88,6 +89,7 @@ project("SDL2") "SDL2/include/SDL_test_images.h", "SDL2/include/SDL_test_log.h", "SDL2/include/SDL_test_md5.h", + "SDL2/include/SDL_test_memory.h", "SDL2/include/SDL_test_random.h", "SDL2/include/SDL_thread.h", "SDL2/include/SDL_timer.h", @@ -114,6 +116,7 @@ project("SDL2") "SDL2/src/dynapi/SDL_dynapi_procs.h", "SDL2/src/events/blank_cursor.h", "SDL2/src/events/default_cursor.h", + "SDL2/src/events/scancodes_windows.h", "SDL2/src/events/SDL_clipboardevents_c.h", "SDL2/src/events/SDL_displayevents_c.h", "SDL2/src/events/SDL_dropevents_c.h", @@ -124,16 +127,20 @@ project("SDL2") "SDL2/src/events/SDL_sysevents.h", "SDL2/src/events/SDL_touch_c.h", "SDL2/src/events/SDL_windowevents_c.h", + "SDL2/src/haptic/SDL_haptic_c.h", "SDL2/src/haptic/SDL_syshaptic.h", "SDL2/src/haptic/windows/SDL_dinputhaptic_c.h", "SDL2/src/haptic/windows/SDL_windowshaptic_c.h", "SDL2/src/haptic/windows/SDL_xinputhaptic_c.h", "SDL2/src/hidapi/hidapi/hidapi.h", + "SDL2/src/hidapi/SDL_hidapi.h", "SDL2/src/joystick/controller_type.h", "SDL2/src/joystick/hidapi/SDL_hidapijoystick_c.h", + "SDL2/src/joystick/hidapi/SDL_hidapi_rumble.h", "SDL2/src/joystick/SDL_gamecontrollerdb.h", "SDL2/src/joystick/SDL_joystick_c.h", "SDL2/src/joystick/SDL_sysjoystick.h", + "SDL2/src/joystick/usb_ids.h", "SDL2/src/joystick/virtual/SDL_virtualjoystick_c.h", "SDL2/src/joystick/windows/SDL_dinputjoystick_c.h", "SDL2/src/joystick/windows/SDL_rawinputjoystick_c.h", @@ -142,11 +149,14 @@ project("SDL2") "SDL2/src/libm/math_libm.h", "SDL2/src/libm/math_private.h", "SDL2/src/locale/SDL_syslocale.h", + "SDL2/src/misc/SDL_sysurl.h", + "SDL2/src/power/SDL_syspower.h", "SDL2/src/render/direct3d11/SDL_shaders_d3d11.h", "SDL2/src/render/direct3d/SDL_shaders_d3d.h", + "SDL2/src/render/opengles2/SDL_gles2funcs.h", + "SDL2/src/render/opengles2/SDL_shaders_gles2.h", "SDL2/src/render/opengl/SDL_glfuncs.h", "SDL2/src/render/opengl/SDL_shaders_gl.h", - "SDL2/src/render/opengles/SDL_glesfuncs.h", "SDL2/src/render/SDL_d3dmath.h", "SDL2/src/render/SDL_sysrender.h", "SDL2/src/render/SDL_yuv_sw_c.h", @@ -158,8 +168,11 @@ project("SDL2") "SDL2/src/render/software/SDL_drawpoint.h", "SDL2/src/render/software/SDL_render_sw_c.h", "SDL2/src/render/software/SDL_rotate.h", + "SDL2/src/SDL_assert_c.h", "SDL2/src/SDL_dataqueue.h", "SDL2/src/SDL_error_c.h", + "SDL2/src/SDL_hints_c.h", + "SDL2/src/SDL_internal.h", "SDL2/src/sensor/dummy/SDL_dummysensor.h", "SDL2/src/sensor/SDL_sensor_c.h", "SDL2/src/sensor/SDL_syssensor.h", @@ -173,10 +186,32 @@ project("SDL2") "SDL2/src/video/dummy/SDL_nullevents_c.h", "SDL2/src/video/dummy/SDL_nullframebuffer_c.h", "SDL2/src/video/dummy/SDL_nullvideo.h", + "SDL2/src/video/khronos/vulkan/vk_icd.h", + "SDL2/src/video/khronos/vulkan/vk_layer.h", + "SDL2/src/video/khronos/vulkan/vk_platform.h", + "SDL2/src/video/khronos/vulkan/vk_sdk_platform.h", + "SDL2/src/video/khronos/vulkan/vulkan.h", + "SDL2/src/video/khronos/vulkan/vulkan.hpp", + "SDL2/src/video/khronos/vulkan/vulkan_android.h", + "SDL2/src/video/khronos/vulkan/vulkan_beta.h", + "SDL2/src/video/khronos/vulkan/vulkan_core.h", + "SDL2/src/video/khronos/vulkan/vulkan_directfb.h", + "SDL2/src/video/khronos/vulkan/vulkan_fuchsia.h", + "SDL2/src/video/khronos/vulkan/vulkan_ggp.h", + "SDL2/src/video/khronos/vulkan/vulkan_ios.h", + "SDL2/src/video/khronos/vulkan/vulkan_macos.h", + "SDL2/src/video/khronos/vulkan/vulkan_metal.h", + "SDL2/src/video/khronos/vulkan/vulkan_vi.h", + "SDL2/src/video/khronos/vulkan/vulkan_wayland.h", + "SDL2/src/video/khronos/vulkan/vulkan_win32.h", + "SDL2/src/video/khronos/vulkan/vulkan_xcb.h", + "SDL2/src/video/khronos/vulkan/vulkan_xlib.h", + "SDL2/src/video/khronos/vulkan/vulkan_xlib_xrandr.h", "SDL2/src/video/SDL_blit.h", "SDL2/src/video/SDL_blit_auto.h", "SDL2/src/video/SDL_blit_copy.h", "SDL2/src/video/SDL_blit_slow.h", + "SDL2/src/video/SDL_egl_c.h", "SDL2/src/video/SDL_pixels_c.h", "SDL2/src/video/SDL_rect_c.h", "SDL2/src/video/SDL_RLEaccel_c.h", @@ -184,6 +219,7 @@ project("SDL2") "SDL2/src/video/SDL_sysvideo.h", "SDL2/src/video/SDL_vulkan_internal.h", "SDL2/src/video/SDL_yuv_c.h", + "SDL2/src/video/windows/SDL_msctf.h", "SDL2/src/video/windows/SDL_vkeys.h", "SDL2/src/video/windows/SDL_windowsclipboard.h", "SDL2/src/video/windows/SDL_windowsevents.h", @@ -193,12 +229,16 @@ project("SDL2") "SDL2/src/video/windows/SDL_windowsmodes.h", "SDL2/src/video/windows/SDL_windowsmouse.h", "SDL2/src/video/windows/SDL_windowsopengl.h", + "SDL2/src/video/windows/SDL_windowsopengles.h", "SDL2/src/video/windows/SDL_windowsshape.h", + "SDL2/src/video/windows/SDL_windowstaskdialog.h", "SDL2/src/video/windows/SDL_windowsvideo.h", "SDL2/src/video/windows/SDL_windowsvulkan.h", "SDL2/src/video/windows/SDL_windowswindow.h", "SDL2/src/video/windows/wmmsg.h", "SDL2/src/video/yuv2rgb/yuv_rgb.h", + "SDL2/src/video/yuv2rgb/yuv_rgb_sse_func.h", + "SDL2/src/video/yuv2rgb/yuv_rgb_std_func.h", "SDL2/src/atomic/SDL_atomic.c", "SDL2/src/atomic/SDL_spinlock.c", @@ -240,6 +280,7 @@ project("SDL2") "SDL2/src/joystick/dummy/SDL_sysjoystick.c", "SDL2/src/joystick/hidapi/SDL_hidapijoystick.c", "SDL2/src/joystick/hidapi/SDL_hidapi_gamecube.c", + "SDL2/src/joystick/hidapi/SDL_hidapi_luna.c", "SDL2/src/joystick/hidapi/SDL_hidapi_ps4.c", "SDL2/src/joystick/hidapi/SDL_hidapi_ps5.c", "SDL2/src/joystick/hidapi/SDL_hidapi_rumble.c", From ed0a15dcc83542252c2e7d55899f705d08d1260d Mon Sep 17 00:00:00 2001 From: gibbed Date: Sun, 8 Aug 2021 10:20:51 -0500 Subject: [PATCH 20/36] Use AppVeyor vars for extended version info. --- src/xenia/app/emulator_window.cc | 12 ++++++++++- src/xenia/base/main_win.cc | 8 +++++++- xenia-build | 35 ++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 658303906..1b38a8f96 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -54,7 +54,12 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator) " CHECKED" #endif #endif - " (" XE_BUILD_BRANCH "/" XE_BUILD_COMMIT_SHORT "/" XE_BUILD_DATE + " (" +#ifdef XE_BUILD_IS_PR + "PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO + " " XE_BUILD_PR_BRANCH "@" XE_BUILD_PR_COMMIT_SHORT " against " +#endif + XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE ")"; } @@ -426,8 +431,13 @@ void EmulatorWindow::ToggleFullscreen() { void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); } void EmulatorWindow::ShowCommitID() { +#ifdef XE_BUILD_IS_PR + LaunchWebBrowser( + "https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER); +#else LaunchWebBrowser( "https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/"); +#endif } void EmulatorWindow::UpdateTitle() { diff --git a/src/xenia/base/main_win.cc b/src/xenia/base/main_win.cc index 6cbf8c9ae..5d68811f1 100644 --- a/src/xenia/base/main_win.cc +++ b/src/xenia/base/main_win.cc @@ -162,7 +162,13 @@ int Main() { } // Print version info. - XELOGI("Build: " XE_BUILD_BRANCH " / " XE_BUILD_COMMIT " on " XE_BUILD_DATE); + XELOGI( + "Build: " +#ifdef XE_BUILD_IS_PR + "PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO " " XE_BUILD_PR_BRANCH + "@" XE_BUILD_PR_COMMIT_SHORT " against " +#endif + XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE); // Request high performance timing. if (cvars::win32_high_freq) { diff --git a/xenia-build b/xenia-build index 6bba02562..7586582ab 100755 --- a/xenia-build +++ b/xenia-build @@ -262,13 +262,31 @@ def generate_version_h(): """Generates a build/version.h file that contains current git info. """ header_file = 'build/version.h' - if git_is_repository(): + pr_number = 0 + pr_repo_name = "" + pr_branch_name = "" + pr_commit = "" + pr_commit_short = "" + if os.getenv('APPVEYOR') == 'True': + branch_name = os.getenv('APPVEYOR_REPO_BRANCH') + commit = os.getenv('APPVEYOR_REPO_COMMIT') + commit_short = commit[:9] + pr_number = os.getenv('APPVEYOR_PULL_REQUEST_NUMBER') + if not pr_number: + pr_number = 0 + else: + pr_repo_name = os.getenv('APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME') + pr_branch_name = os.getenv('APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH') + pr_commit = os.getenv('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') + pr_commit_short = pr_commit[:9] + elif git_is_repository(): (branch_name, commit, commit_short) = git_get_head_info() else: branch_name = 'tarball' commit = ':(-dont-do-this' commit_short = ':(' + # header contents_new = '''// Autogenerated by `xb premake`. #ifndef GENERATED_VERSION_H_ #define GENERATED_VERSION_H_ @@ -276,9 +294,22 @@ def generate_version_h(): #define XE_BUILD_COMMIT "{}" #define XE_BUILD_COMMIT_SHORT "{}" #define XE_BUILD_DATE __DATE__ -#endif // GENERATED_VERSION_H_ '''.format(branch_name, commit, commit_short) + # PR info (if available) + if pr_number != 0: + contents_new += '''#define XE_BUILD_IS_PR +#define XE_BUILD_PR_NUMBER "{}" +#define XE_BUILD_PR_REPO "{}" +#define XE_BUILD_PR_BRANCH "{}" +#define XE_BUILD_PR_COMMIT "{}" +#define XE_BUILD_PR_COMMIT_SHORT "{}" +'''.format(pr_number, pr_repo_name, pr_branch_name, pr_commit, pr_commit_short) + + # footer + contents_new += '''#endif // GENERATED_VERSION_H_ +''' + contents_old = None if os.path.exists(header_file) and os.path.getsize(header_file) < 1024: with open(header_file, 'r') as f: From 05bfdb02e50a9ed11619d450390acc3055e9c12a Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 9 Jul 2021 08:50:13 +0200 Subject: [PATCH 21/36] [XAM] Return correct error code from GetServiceInfo --- src/xenia/kernel/xam/apps/xlivebase_app.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/kernel/xam/apps/xlivebase_app.cc b/src/xenia/kernel/xam/apps/xlivebase_app.cc index b8c72caf1..45440fa55 100644 --- a/src/xenia/kernel/xam/apps/xlivebase_app.cc +++ b/src/xenia/kernel/xam/apps/xlivebase_app.cc @@ -47,7 +47,7 @@ X_HRESULT XLiveBaseApp::DispatchMessageSync(uint32_t message, // XONLINE_SERVICE_INFO structure. XELOGD("CXLiveLogon::GetServiceInfo({:08X}, {:08X})", buffer_ptr, buffer_length); - return 1229; // ERROR_CONNECTION_INVALID + return 0x80151802; // ERROR_CONNECTION_INVALID } case 0x00058020: { // 0x00058004 is called right before this. From f2c706f943fc19d634203e761caa8fc25f469df1 Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 5 Jul 2021 04:48:33 +0100 Subject: [PATCH 22/36] [App] Add cache:\ mount for older games that use it --- src/xenia/app/xenia_main.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 342d5cc2b..91c71d822 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -329,6 +329,22 @@ int xenia_main(const std::vector& args) { emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1"); } } + + // Some (older?) games try accessing cache:\ too + // NOTE: this must be registered _after_ the cache0/cache1 devices, due to + // substring/start_with logic inside VirtualFileSystem::ResolvePath, else + // accesses to those devices will go here instead + auto cache_device = + std::make_unique("\\CACHE", "cache", false); + if (!cache_device->Initialize()) { + XELOGE("Unable to scan cache path"); + } else { + if (!emulator->file_system()->RegisterDevice(std::move(cache_device))) { + XELOGE("Unable to register cache path"); + } else { + emulator->file_system()->RegisterSymbolicLink("cache:", "\\CACHE"); + } + } } // Set a debug handler. From eaab7998f7e1ea4ea484175c4da68f0dbb13911f Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 5 Jul 2021 04:50:14 +0100 Subject: [PATCH 23/36] [Kernel/XAM] Run XAM-tasks in seperate thread, stub XamTaskShouldExit --- src/xenia/kernel/xam/xam_task.cc | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/xenia/kernel/xam/xam_task.cc b/src/xenia/kernel/xam/xam_task.cc index facac462c..58f314079 100644 --- a/src/xenia/kernel/xam/xam_task.cc +++ b/src/xenia/kernel/xam/xam_task.cc @@ -11,6 +11,7 @@ #include "xenia/base/string_util.h" #include "xenia/cpu/processor.h" #include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/xam_module.h" #include "xenia/kernel/xam/xam_private.h" @@ -40,24 +41,37 @@ static_assert_size(XTASK_MESSAGE, 0x1C); dword_result_t XamTaskSchedule(lpvoid_t callback, pointer_t message, - dword_t unknown, lpdword_t handle_ptr) { - assert_zero(unknown); - + lpdword_t unknown, lpdword_t handle_ptr) { // TODO(gibbed): figure out what this is for *handle_ptr = 12345; - XELOGW("!! Executing scheduled task ({:08X}) synchronously, PROBABLY BAD !! ", + uint32_t stack_size = kernel_state()->GetExecutableModule()->stack_size(); + + // Stack must be aligned to 16kb pages + stack_size = std::max((uint32_t)0x4000, ((stack_size + 0xFFF) & 0xFFFFF000)); + + auto thread = + object_ref(new XThread(kernel_state(), stack_size, 0, callback, + message.guest_address(), 0, true)); + + X_STATUS result = thread->Create(); + + if (XFAILED(result)) { + // Failed! + XELOGE("XAM task creation failed: {:08X}", result); + return result; + } + + XELOGD("XAM task ({:08X}) scheduled asynchronously", callback.guest_address()); - // TODO(gibbed): this is supposed to be async... let's cheat. - auto thread_state = XThread::GetCurrentThread()->thread_state(); - uint64_t args[] = {message.guest_address()}; - auto result = kernel_state()->processor()->Execute(thread_state, callback, - args, xe::countof(args)); return X_STATUS_SUCCESS; } DECLARE_XAM_EXPORT2(XamTaskSchedule, kNone, kImplemented, kSketchy); +dword_result_t XamTaskShouldExit(dword_t r3) { return 0; } +DECLARE_XAM_EXPORT2(XamTaskShouldExit, kNone, kStub, kSketchy); + void RegisterTaskExports(xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) {} From e5725b5877f47c6447491a0e626453e8b538b102 Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 5 Jul 2021 04:54:15 +0100 Subject: [PATCH 24/36] [Kernel] Support XFileAlignmentInformation, stub NtDeviceIoControlFile & IoCreateDevice XMountUtilityDrive-related code checks some values returned from NtDeviceIoControlFile, stub just returns values that it seems to accept IoCreateDevice is also used by utility-drive code, writing some values into a pointer returned by it, so stub allocs space so it can write to the pointer without errors. --- src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc | 60 +++++++++++++++++++ src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc | 7 +++ 2 files changed, 67 insertions(+) diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc index 38d557eef..f7dee19b9 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc @@ -674,6 +674,66 @@ dword_result_t FscSetCacheElementCount(dword_t unk_0, dword_t unk_1) { } DECLARE_XBOXKRNL_EXPORT1(FscSetCacheElementCount, kFileSystem, kStub); +dword_result_t NtDeviceIoControlFile( + dword_t handle, dword_t event_handle, dword_t apc_routine, + dword_t apc_context, dword_t io_status_block, dword_t io_control_code, + lpvoid_t input_buffer, dword_t input_buffer_len, lpvoid_t output_buffer, + dword_t output_buffer_len) { + // Called by XMountUtilityDrive cache-mounting code + // (checks if the returned values look valid, values below seem to pass the + // checks) + const uint32_t cache_size = 0xFF000; + + const uint32_t X_IOCTL_DISK_GET_DRIVE_GEOMETRY = 0x70000; + const uint32_t X_IOCTL_DISK_GET_PARTITION_INFO = 0x74004; + + if (io_control_code == X_IOCTL_DISK_GET_DRIVE_GEOMETRY) { + if (output_buffer_len < 0x8) { + assert_always(); + return X_STATUS_BUFFER_TOO_SMALL; + } + xe::store_and_swap(output_buffer, cache_size / 512); + xe::store_and_swap(output_buffer + 4, 512); + } else if (io_control_code == X_IOCTL_DISK_GET_PARTITION_INFO) { + if (output_buffer_len < 0x10) { + assert_always(); + return X_STATUS_BUFFER_TOO_SMALL; + } + xe::store_and_swap(output_buffer, 0); + xe::store_and_swap(output_buffer + 8, cache_size); + } else { + XELOGD("NtDeviceIoControlFile(0x{:X}) - unhandled IOCTL!", + uint32_t(io_control_code)); + assert_always(); + return X_STATUS_INVALID_PARAMETER; + } + + return X_STATUS_SUCCESS; +} +DECLARE_XBOXKRNL_EXPORT1(NtDeviceIoControlFile, kFileSystem, kStub); + +dword_result_t IoCreateDevice(dword_t device_struct, dword_t r4, dword_t r5, + dword_t r6, dword_t r7, lpdword_t out_struct) { + // Called from XMountUtilityDrive XAM-task code + // That code tries writing things to a pointer at out_struct+0x18 + // We'll alloc some scratch space for it so it doesn't cause any exceptions + + // 0x24 is guessed size from accesses to out_struct - likely incorrect + auto out_guest = kernel_memory()->SystemHeapAlloc(0x24); + + auto out = kernel_memory()->TranslateVirtual(out_guest); + memset(out, 0, 0x24); + + // XMountUtilityDrive writes some kind of header here + // 0x1000 bytes should be enough to store it + auto out_guest2 = kernel_memory()->SystemHeapAlloc(0x1000); + xe::store_and_swap(out + 0x18, out_guest2); + + *out_struct = out_guest; + return X_STATUS_SUCCESS; +} +DECLARE_XBOXKRNL_EXPORT1(IoCreateDevice, kFileSystem, kStub); + void RegisterIoExports(xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) {} diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc index 0add7390a..e43db31e6 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc @@ -126,6 +126,13 @@ dword_result_t NtQueryInformationFile( out_length = sizeof(*info); break; } + case XFileAlignmentInformation: { + // Requested by XMountUtilityDrive XAM-task + auto info = info_ptr.as(); + *info = 0; // FILE_BYTE_ALIGNMENT? + out_length = sizeof(*info); + break; + } default: { // Unsupported, for now. assert_always(); From bf8138a886f08216d2f5d6bbdd12e82e870db902 Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 5 Jul 2021 04:58:29 +0100 Subject: [PATCH 25/36] [VFS] Add NullDevice (returns success for all calls), handle \Device\Harddisk0\ with it XMountUtilityDrive code tries reading/writing from \Device\Harddisk0\Cache0 / Cache1 / Partition0, NullDevice handling \Device\Harddisk0 will make that code think that the reads/writes were successful, so the utility-drive mount can proceed without failing. --- src/xenia/emulator.cc | 22 +++++++++- src/xenia/vfs/devices/null_device.cc | 62 ++++++++++++++++++++++++++++ src/xenia/vfs/devices/null_device.h | 57 +++++++++++++++++++++++++ src/xenia/vfs/devices/null_entry.cc | 55 ++++++++++++++++++++++++ src/xenia/vfs/devices/null_entry.h | 42 +++++++++++++++++++ src/xenia/vfs/devices/null_file.cc | 52 +++++++++++++++++++++++ src/xenia/vfs/devices/null_file.h | 40 ++++++++++++++++++ 7 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/xenia/vfs/devices/null_device.cc create mode 100644 src/xenia/vfs/devices/null_device.h create mode 100644 src/xenia/vfs/devices/null_entry.cc create mode 100644 src/xenia/vfs/devices/null_entry.h create mode 100644 src/xenia/vfs/devices/null_file.cc create mode 100644 src/xenia/vfs/devices/null_file.h diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 36a56c946..92ddabe4f 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -42,6 +42,7 @@ #include "xenia/ui/imgui_dialog.h" #include "xenia/vfs/devices/disc_image_device.h" #include "xenia/vfs/devices/host_path_device.h" +#include "xenia/vfs/devices/null_device.h" #include "xenia/vfs/devices/stfs_container_device.h" #include "xenia/vfs/virtual_file_system.h" @@ -279,7 +280,7 @@ X_STATUS Emulator::LaunchXexFile(const std::filesystem::path& path) { // and then get that symlinked to game:\, so // -> game:\foo.xex - auto mount_path = "\\Device\\Harddisk0\\Partition0"; + auto mount_path = "\\Device\\Harddisk0\\Partition1"; // Register the local directory in the virtual filesystem. auto parent_path = path.parent_path(); @@ -672,6 +673,25 @@ static std::string format_version(xex2_version version) { X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, const std::string_view module_path) { + // Setup NullDevices for raw HDD partition accesses + // Cache/STFC code baked into games tries reading/writing to these + // By using a NullDevice that just returns success to all IO requests it + // should allow games to believe cache/raw disk was accessed successfully + + // NOTE: this should probably be moved to xenia_main.cc, but right now we need + // to register the \Device\Harddisk0\ NullDevice _after_ the + // \Device\Harddisk0\Partition1 HostPathDevice, otherwise requests to + // Partition1 will go to this. Registering during CompleteLaunch allows us to + // make sure any HostPathDevices are ready beforehand. + // (see comment above cache:\ device registration for more info about why) + auto null_paths = {std::string("\\Partition0"), std::string("\\Cache0"), + std::string("\\Cache1")}; + auto null_device = + std::make_unique("\\Device\\Harddisk0", null_paths); + if (null_device->Initialize()) { + file_system_->RegisterDevice(std::move(null_device)); + } + // Reset state. title_id_ = std::nullopt; title_name_ = ""; diff --git a/src/xenia/vfs/devices/null_device.cc b/src/xenia/vfs/devices/null_device.cc new file mode 100644 index 000000000..e7b014d7b --- /dev/null +++ b/src/xenia/vfs/devices/null_device.cc @@ -0,0 +1,62 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/vfs/devices/null_device.h" + +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/kernel/xfile.h" +#include "xenia/vfs/devices/null_entry.h" + +namespace xe { +namespace vfs { + +NullDevice::NullDevice(const std::string& mount_path, + const std::initializer_list& null_paths) + : Device(mount_path), null_paths_(null_paths), name_("NullDevice") {} + +NullDevice::~NullDevice() = default; + +bool NullDevice::Initialize() { + auto root_entry = new NullEntry(this, nullptr, mount_path_); + root_entry->attributes_ = kFileAttributeDirectory; + root_entry_ = std::unique_ptr(root_entry); + + for (auto path : null_paths_) { + auto child = NullEntry::Create(this, root_entry, path); + root_entry->children_.push_back(std::unique_ptr(child)); + } + return true; +} + +void NullDevice::Dump(StringBuffer* string_buffer) { + auto global_lock = global_critical_region_.Acquire(); + root_entry_->Dump(string_buffer, 0); +} + +Entry* NullDevice::ResolvePath(const std::string_view path) { + XELOGFS("NullDevice::ResolvePath({})", path); + + auto root = root_entry_.get(); + if (path.empty()) { + return root_entry_.get(); + } + + for (auto& child : root->children()) { + if (!strcasecmp(child->path().c_str(), path.data())) { + return child.get(); + } + } + + return nullptr; +} + +} // namespace vfs +} // namespace xe diff --git a/src/xenia/vfs/devices/null_device.h b/src/xenia/vfs/devices/null_device.h new file mode 100644 index 000000000..6ac8f2a35 --- /dev/null +++ b/src/xenia/vfs/devices/null_device.h @@ -0,0 +1,57 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_VFS_DEVICES_NULL_DEVICE_H_ +#define XENIA_VFS_DEVICES_NULL_DEVICE_H_ + +#include + +#include "xenia/vfs/device.h" + +namespace xe { +namespace vfs { + +class NullEntry; + +class NullDevice : public Device { + public: + NullDevice(const std::string& mount_path, + const std::initializer_list& null_paths); + ~NullDevice() override; + + bool Initialize() override; + void Dump(StringBuffer* string_buffer) override; + Entry* ResolvePath(const std::string_view path) override; + + bool is_read_only() const override { return false; } + + const std::string& name() const override { return name_; } + uint32_t attributes() const override { return 0; } + uint32_t component_name_max_length() const override { return 40; } + + uint32_t total_allocation_units() const override { return 0x10; } + uint32_t available_allocation_units() const override { return 0x10; } + + // STFC/cache code seems to require the product of the next two to equal + // 0x10000 + uint32_t sectors_per_allocation_unit() const override { return 0x80; } + + // STFC requires <= 0x1000 + uint32_t bytes_per_sector() const override { return 0x200; } + + private: + std::string name_; + std::unique_ptr root_entry_; + std::vector null_paths_; +}; + +} // namespace vfs +} // namespace xe + +#endif // XENIA_VFS_DEVICES_NULL_DEVICE_H_ diff --git a/src/xenia/vfs/devices/null_entry.cc b/src/xenia/vfs/devices/null_entry.cc new file mode 100644 index 000000000..45b511c40 --- /dev/null +++ b/src/xenia/vfs/devices/null_entry.cc @@ -0,0 +1,55 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/vfs/devices/null_entry.h" + +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/mapped_memory.h" +#include "xenia/base/math.h" +#include "xenia/base/string.h" +#include "xenia/vfs/device.h" +#include "xenia/vfs/devices/null_file.h" + +namespace xe { +namespace vfs { + +NullEntry::NullEntry(Device* device, Entry* parent, std::string path) + : Entry(device, parent, path) {} + +NullEntry::~NullEntry() = default; + +NullEntry* NullEntry::Create(Device* device, Entry* parent, + const std::string& path) { + auto entry = new NullEntry(device, parent, path); + + entry->create_timestamp_ = 0; + entry->access_timestamp_ = 0; + entry->write_timestamp_ = 0; + + entry->attributes_ = kFileAttributeNormal; + + entry->size_ = 0; + entry->allocation_size_ = 0; + return entry; +} + +X_STATUS NullEntry::Open(uint32_t desired_access, File** out_file) { + if (is_read_only() && (desired_access & (FileAccess::kFileWriteData | + FileAccess::kFileAppendData))) { + XELOGE("Attempting to open file for write access on read-only device"); + return X_STATUS_ACCESS_DENIED; + } + + *out_file = new NullFile(desired_access, this); + return X_STATUS_SUCCESS; +} + +} // namespace vfs +} // namespace xe diff --git a/src/xenia/vfs/devices/null_entry.h b/src/xenia/vfs/devices/null_entry.h new file mode 100644 index 000000000..84f01cb95 --- /dev/null +++ b/src/xenia/vfs/devices/null_entry.h @@ -0,0 +1,42 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_VFS_DEVICES_NULL_ENTRY_H_ +#define XENIA_VFS_DEVICES_NULL_ENTRY_H_ + +#include + +#include "xenia/base/filesystem.h" +#include "xenia/vfs/entry.h" + +namespace xe { +namespace vfs { + +class NullDevice; + +class NullEntry : public Entry { + public: + NullEntry(Device* device, Entry* parent, std::string path); + ~NullEntry() override; + + static NullEntry* Create(Device* device, Entry* parent, + const std::string& path); + + X_STATUS Open(uint32_t desired_access, File** out_file) override; + + bool can_map() const override { return false; } + + private: + friend class NullDevice; +}; + +} // namespace vfs +} // namespace xe + +#endif // XENIA_VFS_DEVICES_NULL_ENTRY_H_ diff --git a/src/xenia/vfs/devices/null_file.cc b/src/xenia/vfs/devices/null_file.cc new file mode 100644 index 000000000..e04fd8397 --- /dev/null +++ b/src/xenia/vfs/devices/null_file.cc @@ -0,0 +1,52 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/vfs/devices/null_file.h" + +#include "xenia/vfs/devices/null_entry.h" + +namespace xe { +namespace vfs { + +NullFile::NullFile(uint32_t file_access, NullEntry* entry) + : File(file_access, entry) {} + +NullFile::~NullFile() = default; + +void NullFile::Destroy() { delete this; } + +X_STATUS NullFile::ReadSync(void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_read) { + if (!(file_access_ & FileAccess::kFileReadData)) { + return X_STATUS_ACCESS_DENIED; + } + + return X_STATUS_SUCCESS; +} + +X_STATUS NullFile::WriteSync(const void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_written) { + if (!(file_access_ & + (FileAccess::kFileWriteData | FileAccess::kFileAppendData))) { + return X_STATUS_ACCESS_DENIED; + } + + return X_STATUS_SUCCESS; +} + +X_STATUS NullFile::SetLength(size_t length) { + if (!(file_access_ & FileAccess::kFileWriteData)) { + return X_STATUS_ACCESS_DENIED; + } + + return X_STATUS_SUCCESS; +} + +} // namespace vfs +} // namespace xe diff --git a/src/xenia/vfs/devices/null_file.h b/src/xenia/vfs/devices/null_file.h new file mode 100644 index 000000000..32a6be25c --- /dev/null +++ b/src/xenia/vfs/devices/null_file.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_VFS_DEVICES_NULL_FILE_H_ +#define XENIA_VFS_DEVICES_NULL_FILE_H_ + +#include + +#include "xenia/base/filesystem.h" +#include "xenia/vfs/file.h" + +namespace xe { +namespace vfs { + +class NullEntry; + +class NullFile : public File { + public: + NullFile(uint32_t file_access, NullEntry* entry); + ~NullFile() override; + + void Destroy() override; + + X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset, + size_t* out_bytes_read) override; + X_STATUS WriteSync(const void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_written) override; + X_STATUS SetLength(size_t length) override; +}; + +} // namespace vfs +} // namespace xe + +#endif // XENIA_VFS_DEVICES_NULL_FILE_H_ From f6f524b8148145c5b5b5ac0a79809df979069bc6 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Sun, 11 Oct 2020 17:37:22 +0200 Subject: [PATCH 26/36] Implemented ExLoadedImageName --- src/xenia/kernel/kernel_state.cc | 11 +++++++++++ src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc | 10 ++++++++++ src/xenia/kernel/xboxkrnl/xboxkrnl_module.h | 2 ++ 3 files changed, 23 insertions(+) diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index c0d2e3d20..9c5dcebc2 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -316,6 +316,17 @@ void KernelState::SetExecutableModule(object_ref module) { *variable_ptr = executable_module_->hmodule_ptr(); } + // Setup the kernel's ExLoadedImageName field + export_entry = processor()->export_resolver()->GetExportByOrdinal( + "xboxkrnl.exe", ordinals::ExLoadedImageName); + + if (export_entry) { + char* variable_ptr = + memory()->TranslateVirtual(export_entry->variable_ptr); + xe::string_util::copy_truncating( + variable_ptr, executable_module_->path(), + xboxkrnl::XboxkrnlModule::kExLoadedImageNameSize); + } // Spin up deferred dispatch worker. // TODO(benvanik): move someplace more appropriate (out of ctor, but around // here). diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc index b2aafe294..f6b9846e0 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc @@ -189,6 +189,16 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state) ordinals::XexExecutableModuleHandle, ppXexExecutableModuleHandle); + // ExLoadedImageName (char*) + // The full path to loaded image/xex including its name. + // Used usually in custom dashboards (Aurora) + // Todo(Gliniak): Confirm that official kernel always allocate space for this + // variable. + uint32_t ppExLoadedImageName = + memory_->SystemHeapAlloc(kExLoadedImageNameSize); + export_resolver_->SetVariableMapping( + "xboxkrnl.exe", ordinals::ExLoadedImageName, ppExLoadedImageName); + // ExLoadedCommandLine (char*) // The name of the xex. Not sure this is ever really used on real devices. // Perhaps it's how swap disc/etc data is sent? diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_module.h b/src/xenia/kernel/xboxkrnl/xboxkrnl_module.h index 5a817453c..39a9c95b2 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_module.h +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_module.h @@ -27,6 +27,8 @@ namespace xboxkrnl { class XboxkrnlModule : public KernelModule { public: + static constexpr size_t kExLoadedImageNameSize = 255 + 1; + XboxkrnlModule(Emulator* emulator, KernelState* kernel_state); virtual ~XboxkrnlModule(); From f540c188bf16abf3b3be4ffd00d1154fb12e5254 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 26 Aug 2021 21:18:18 +0300 Subject: [PATCH 27/36] [Lint] Revert incorrect clang-format changes --- src/xenia/cpu/hir/value.cc | 5 +++-- src/xenia/cpu/ppc/ppc_emit_control.cc | 2 +- src/xenia/cpu/xex_module.cc | 7 ++++--- src/xenia/kernel/xam/xam_user.cc | 5 +++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/xenia/cpu/hir/value.cc b/src/xenia/cpu/hir/value.cc index 08de8af9b..28ed07ee7 100644 --- a/src/xenia/cpu/hir/value.cc +++ b/src/xenia/cpu/hir/value.cc @@ -395,8 +395,9 @@ void Value::MulHi(Value* other, bool is_unsigned) { (uint32_t)other->constant.i32) >> 32); } else { - constant.i32 = (int32_t)( - ((int64_t)constant.i32 * (int64_t)other->constant.i32) >> 32); + constant.i32 = + (int32_t)(((int64_t)constant.i32 * (int64_t)other->constant.i32) >> + 32); } break; case INT64_TYPE: diff --git a/src/xenia/cpu/ppc/ppc_emit_control.cc b/src/xenia/cpu/ppc/ppc_emit_control.cc index eafd15492..ec09ea458 100644 --- a/src/xenia/cpu/ppc/ppc_emit_control.cc +++ b/src/xenia/cpu/ppc/ppc_emit_control.cc @@ -810,6 +810,6 @@ void RegisterEmitCategoryControl() { XEREGISTERINSTR(mtmsrd); } -} // namespace cpu +} // namespace ppc } // namespace cpu } // namespace xe diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index aed4f4cb9..672d44666 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -832,9 +832,10 @@ int XexModule::ReadPEHeaders() { // offsetof seems to be unable to find OptionalHeader. #define offsetof1(type, member) ((std::size_t) & (((type*)0)->member)) #define IMAGE_FIRST_SECTION1(ntheader) \ - ((PIMAGE_SECTION_HEADER)( \ - (uint8_t*)ntheader + offsetof1(IMAGE_NT_HEADERS, OptionalHeader) + \ - ((PIMAGE_NT_HEADERS)(ntheader))->FileHeader.SizeOfOptionalHeader)) + ((PIMAGE_SECTION_HEADER)((uint8_t*)ntheader + \ + offsetof1(IMAGE_NT_HEADERS, OptionalHeader) + \ + ((PIMAGE_NT_HEADERS)(ntheader)) \ + ->FileHeader.SizeOfOptionalHeader)) // Quick scan to determine bounds of sections. size_t upper_address = 0; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 2a1d3c3f7..aed0c765b 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -277,8 +277,9 @@ uint32_t xeXamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index, auto setting = user_profile->GetSetting(setting_id); std::memset(out_setting, 0, sizeof(X_USER_READ_PROFILE_SETTING)); - out_setting->from = - !setting || !setting->is_set ? 0 : setting->is_title_specific() ? 2 : 1; + out_setting->from = !setting || !setting->is_set ? 0 + : setting->is_title_specific() ? 2 + : 1; out_setting->user_index = static_cast(user_index); out_setting->setting_id = setting_id; From 6ce5330f5f240f7ea5bcd06a49794480a339860a Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 28 Aug 2021 19:38:24 +0300 Subject: [PATCH 28/36] [UI] Loop thread to main thread WindowedAppContext --- premake5.lua | 12 +- src/xenia/app/emulator_window.cc | 39 +- src/xenia/app/emulator_window.h | 14 +- src/xenia/app/premake5.lua | 2 +- src/xenia/app/xenia_main.cc | 392 +++++++++++------- src/xenia/apu/xaudio2/xaudio2_audio_driver.cc | 143 ++++++- src/xenia/apu/xaudio2/xaudio2_audio_driver.h | 33 ++ src/xenia/base/app_win32.manifest | 14 + src/xenia/base/console.h | 22 + src/xenia/base/{main.h => console_app_main.h} | 42 +- src/xenia/base/console_app_main_android.cc | 10 + ...ain_posix.cc => console_app_main_posix.cc} | 34 +- src/xenia/base/console_app_main_win.cc | 36 ++ src/xenia/base/console_posix.cc | 21 + src/xenia/base/console_win.cc | 60 +++ src/xenia/base/cvar.cc | 2 +- src/xenia/base/logging.cc | 2 +- src/xenia/base/main_init_android.cc | 10 + src/xenia/base/main_init_posix.cc | 6 +- src/xenia/base/main_init_win.cc | 3 +- src/xenia/base/main_win.cc | 136 +----- src/xenia/base/main_win.h | 29 ++ src/xenia/base/premake5.lua | 3 +- src/xenia/cpu/ppc/testing/ppc_testing_main.cc | 6 +- .../ppc/testing/ppc_testing_native_main.cc | 17 +- src/xenia/cpu/ppc/testing/premake5.lua | 4 +- src/xenia/cpu/testing/sandbox_main.cc | 8 +- src/xenia/cpu/testing/util.h | 1 - src/xenia/debug/ui/debug_window.cc | 40 +- src/xenia/debug/ui/debug_window.h | 15 +- src/xenia/emulator.cc | 6 +- src/xenia/gpu/d3d12/d3d12_graphics_system.cc | 2 +- src/xenia/gpu/d3d12/d3d12_trace_dump_main.cc | 8 +- .../gpu/d3d12/d3d12_trace_viewer_main.cc | 29 +- src/xenia/gpu/d3d12/premake5.lua | 4 +- src/xenia/gpu/graphics_system.cc | 33 +- src/xenia/gpu/null/null_graphics_system.cc | 2 +- src/xenia/gpu/premake5.lua | 2 +- src/xenia/gpu/shader_compiler_main.cc | 7 +- src/xenia/gpu/trace_dump.cc | 2 +- src/xenia/gpu/trace_player.cc | 5 +- src/xenia/gpu/trace_player.h | 4 +- src/xenia/gpu/trace_viewer.cc | 80 ++-- src/xenia/gpu/trace_viewer.h | 20 +- src/xenia/gpu/vulkan/premake5.lua | 4 +- .../gpu/vulkan/vulkan_graphics_system.cc | 2 +- .../gpu/vulkan/vulkan_trace_dump_main.cc | 8 +- .../gpu/vulkan/vulkan_trace_viewer_main.cc | 27 +- src/xenia/hid/hid_demo.cc | 208 +++++----- src/xenia/hid/premake5.lua | 2 +- src/xenia/hid/sdl/sdl_input_driver.cc | 18 +- src/xenia/kernel/xam/xam_nui.cc | 20 +- src/xenia/kernel/xam/xam_ui.cc | 29 +- src/xenia/kernel/xthread.cc | 18 - .../tools/api-scanner/api_scanner_loader.h | 5 +- .../tools/api-scanner/api_scanner_main.cc | 18 +- src/xenia/ui/d3d12/d3d12_provider.cc | 7 +- src/xenia/ui/d3d12/d3d12_provider.h | 4 +- src/xenia/ui/d3d12/d3d12_window_demo.cc | 32 +- src/xenia/ui/d3d12/premake5.lua | 2 +- src/xenia/ui/file_picker_win.cc | 34 +- src/xenia/ui/graphics_provider.h | 7 +- src/xenia/ui/loop.cc | 37 -- src/xenia/ui/loop.h | 45 -- src/xenia/ui/loop_gtk.cc | 81 ---- src/xenia/ui/loop_win.cc | 131 ------ src/xenia/ui/loop_win.h | 74 ---- src/xenia/ui/premake5.lua | 1 + src/xenia/ui/vulkan/premake5.lua | 2 +- src/xenia/ui/vulkan/vulkan_provider.cc | 7 +- src/xenia/ui/vulkan/vulkan_provider.h | 4 +- src/xenia/ui/vulkan/vulkan_window_demo.cc | 32 +- src/xenia/ui/window.cc | 4 +- src/xenia/ui/window.h | 21 +- src/xenia/ui/window_demo.cc | 91 ++-- src/xenia/ui/window_demo.h | 44 ++ src/xenia/ui/window_gtk.cc | 14 +- src/xenia/ui/window_gtk.h | 3 +- src/xenia/ui/window_win.cc | 59 +-- src/xenia/ui/window_win.h | 3 +- src/xenia/ui/windowed_app.h | 129 ++++++ src/xenia/ui/windowed_app_context.cc | 180 ++++++++ src/xenia/ui/windowed_app_context.h | 184 ++++++++ src/xenia/ui/windowed_app_context_android.cc | 67 +++ src/xenia/ui/windowed_app_context_android.h | 61 +++ src/xenia/ui/windowed_app_context_gtk.cc | 112 +++++ src/xenia/ui/windowed_app_context_gtk.h | 46 ++ src/xenia/ui/windowed_app_context_win.cc | 130 ++++++ src/xenia/ui/windowed_app_context_win.h | 58 +++ src/xenia/ui/windowed_app_main_posix.cc | 64 +++ src/xenia/ui/windowed_app_main_win.cc | 70 ++++ src/xenia/vfs/premake5.lua | 2 +- src/xenia/vfs/vfs_dump.cc | 9 +- tools/build/scripts/test_suite.lua | 2 +- tools/build/src/test_suite_main.cc | 6 +- 95 files changed, 2343 insertions(+), 1235 deletions(-) create mode 100644 src/xenia/base/app_win32.manifest create mode 100644 src/xenia/base/console.h rename src/xenia/base/{main.h => console_app_main.h} (51%) create mode 100644 src/xenia/base/console_app_main_android.cc rename src/xenia/base/{main_posix.cc => console_app_main_posix.cc} (64%) create mode 100644 src/xenia/base/console_app_main_win.cc create mode 100644 src/xenia/base/console_posix.cc create mode 100644 src/xenia/base/console_win.cc create mode 100644 src/xenia/base/main_init_android.cc create mode 100644 src/xenia/base/main_win.h delete mode 100644 src/xenia/ui/loop.cc delete mode 100644 src/xenia/ui/loop.h delete mode 100644 src/xenia/ui/loop_gtk.cc delete mode 100644 src/xenia/ui/loop_win.cc delete mode 100644 src/xenia/ui/loop_win.h create mode 100644 src/xenia/ui/window_demo.h create mode 100644 src/xenia/ui/windowed_app.h create mode 100644 src/xenia/ui/windowed_app_context.cc create mode 100644 src/xenia/ui/windowed_app_context.h create mode 100644 src/xenia/ui/windowed_app_context_android.cc create mode 100644 src/xenia/ui/windowed_app_context_android.h create mode 100644 src/xenia/ui/windowed_app_context_gtk.cc create mode 100644 src/xenia/ui/windowed_app_context_gtk.h create mode 100644 src/xenia/ui/windowed_app_context_win.cc create mode 100644 src/xenia/ui/windowed_app_context_win.h create mode 100644 src/xenia/ui/windowed_app_main_posix.cc create mode 100644 src/xenia/ui/windowed_app_main_win.cc diff --git a/premake5.lua b/premake5.lua index e44f81c71..55a0523a4 100644 --- a/premake5.lua +++ b/premake5.lua @@ -189,6 +189,12 @@ filter("platforms:Windows") "bcrypt", }) +-- Embed the manifest for things like dependencies and DPI awareness. +filter({"platforms:Windows", "kind:ConsoleApp or WindowedApp"}) + files({ + "src/xenia/base/app_win32.manifest" + }) + -- Create scratch/ path if not os.isdir("scratch") then os.mkdir("scratch") @@ -243,9 +249,13 @@ workspace("xenia") include("third_party/SDL2.lua") end - -- Disable treating warnings as fatal errors for all third party projects: + -- Disable treating warnings as fatal errors for all third party projects, as + -- well as other things relevant only to Xenia itself. for _, prj in ipairs(premake.api.scope.current.solution.projects) do project(prj.name) + removefiles({ + "src/xenia/base/app_win32.manifest" + }) removeflags({ "FatalWarnings", }) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 1b38a8f96..d8e792aa2 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -11,6 +11,7 @@ #include "third_party/fmt/include/fmt/format.h" #include "third_party/imgui/imgui.h" +#include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/cvar.h" #include "xenia/base/debugging.h" @@ -42,10 +43,11 @@ using xe::ui::UIEvent; const std::string kBaseTitle = "xenia"; -EmulatorWindow::EmulatorWindow(Emulator* emulator) +EmulatorWindow::EmulatorWindow(Emulator* emulator, + ui::WindowedAppContext& app_context) : emulator_(emulator), - loop_(ui::Loop::Create()), - window_(ui::Window::Create(loop_.get(), kBaseTitle)) { + app_context_(app_context), + window_(ui::Window::Create(app_context, kBaseTitle)) { base_title_ = kBaseTitle + #ifdef DEBUG #if _NO_DEBUG_HEAP == 1 @@ -63,23 +65,14 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator) ")"; } -EmulatorWindow::~EmulatorWindow() { - loop_->PostSynchronous([this]() { window_.reset(); }); -} - -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("Windowing Loop"); - xe::Profiler::ThreadEnter("Windowing Loop"); - - if (!emulator_window->Initialize()) { - xe::FatalError("Failed to initialize main window"); - return; - } - }); - +std::unique_ptr EmulatorWindow::Create( + Emulator* emulator, ui::WindowedAppContext& app_context) { + assert_true(app_context.IsInUIThread()); + std::unique_ptr emulator_window( + new EmulatorWindow(emulator, app_context)); + if (!emulator_window->Initialize()) { + return nullptr; + } return emulator_window; } @@ -91,8 +84,8 @@ bool EmulatorWindow::Initialize() { UpdateTitle(); - window_->on_closed.AddListener([this](UIEvent* e) { loop_->Quit(); }); - loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); }); + window_->on_closed.AddListener( + [this](UIEvent* e) { app_context_.QuitFromUIThread(); }); window_->on_file_drop.AddListener( [this](FileDropEvent* e) { FileDrop(e->filename()); }); @@ -313,7 +306,7 @@ void EmulatorWindow::FileOpen() { file_picker->set_multi_selection(false); file_picker->set_title("Select Content Package"); file_picker->set_extensions({ - {"Supported Files", "*.iso;*.xex;*.xcp;*.*"}, + {"Supported Files", "*.iso;*.xex;*.*"}, {"Disc Image (*.iso)", "*.iso"}, {"Xbox Executable (*.xex)", "*.xex"}, //{"Content Package (*.xcp)", "*.xcp" }, diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 4d29d9a5b..6dfb69080 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -13,9 +13,9 @@ #include #include -#include "xenia/ui/loop.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" namespace xe { @@ -27,12 +27,11 @@ namespace app { class EmulatorWindow { public: - virtual ~EmulatorWindow(); - - static std::unique_ptr Create(Emulator* emulator); + static std::unique_ptr Create( + Emulator* emulator, ui::WindowedAppContext& app_context); Emulator* emulator() const { return emulator_; } - ui::Loop* loop() const { return loop_.get(); } + ui::WindowedAppContext& app_context() const { return app_context_; } ui::Window* window() const { return window_.get(); } void UpdateTitle(); @@ -40,7 +39,8 @@ class EmulatorWindow { void SetInitializingShaderStorage(bool initializing); private: - explicit EmulatorWindow(Emulator* emulator); + explicit EmulatorWindow(Emulator* emulator, + ui::WindowedAppContext& app_context); bool Initialize(); @@ -60,7 +60,7 @@ class EmulatorWindow { void ShowCommitID(); Emulator* emulator_; - std::unique_ptr loop_; + ui::WindowedAppContext& app_context_; std::unique_ptr window_; std::string base_title_; uint64_t cursor_hide_time_ = 0; diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 66f8beaa2..64ebf0db2 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -52,8 +52,8 @@ project("xenia-app") local_platform_files() files({ "xenia_main.cc", - "../base/main_"..platform_suffix..".cc", "../base/main_init_"..platform_suffix..".cc", + "../ui/windowed_app_main_"..platform_suffix..".cc", }) resincludedirs({ diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 91c71d822..39a2a72b4 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -7,18 +7,29 @@ ****************************************************************************** */ +#include +#include +#include +#include +#include +#include +#include + #include "xenia/app/discord/discord_presence.h" #include "xenia/app/emulator_window.h" #include "xenia/base/cvar.h" #include "xenia/base/debugging.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" +#include "xenia/base/platform.h" #include "xenia/base/profiling.h" #include "xenia/base/threading.h" #include "xenia/config.h" #include "xenia/debug/ui/debug_window.h" #include "xenia/emulator.h" #include "xenia/ui/file_picker.h" +#include "xenia/ui/window.h" +#include "xenia/ui/windowed_app.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/vfs/devices/host_path_device.h" // Available audio systems: @@ -44,7 +55,6 @@ #endif // XE_PLATFORM_WIN32 #include "third_party/fmt/include/fmt/format.h" -#include "third_party/xbyak/xbyak/xbyak_util.h" DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU"); DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]", @@ -91,83 +101,134 @@ DEFINE_bool(discord, true, "Enable Discord rich presence", "General"); namespace xe { namespace app { -template -class Factory { +class EmulatorApp final : public xe::ui::WindowedApp { + public: + static std::unique_ptr Create( + xe::ui::WindowedAppContext& app_context) { + return std::unique_ptr(new EmulatorApp(app_context)); + } + + ~EmulatorApp(); + + bool OnInitialize() override; + + protected: + void OnDestroy() override; + private: - struct Creator { - std::string name; - std::function is_available; - std::function(Args...)> instantiate; + template + class Factory { + private: + struct Creator { + std::string name; + std::function is_available; + std::function(Args...)> instantiate; + }; + + std::vector creators_; + + public: + void Add(const std::string_view name, std::function is_available, + std::function(Args...)> instantiate) { + creators_.push_back({std::string(name), is_available, instantiate}); + } + + void Add(const std::string_view name, + std::function(Args...)> instantiate) { + auto always_available = []() { return true; }; + Add(name, always_available, instantiate); + } + + template + void Add(const std::string_view name) { + Add(name, DT::IsAvailable, [](Args... args) { + return std::make_unique
(std::forward(args)...); + }); + } + + std::unique_ptr Create(const std::string_view name, Args... args) { + if (!name.empty() && name != "any") { + auto it = std::find_if( + creators_.cbegin(), creators_.cend(), + [&name](const auto& f) { return name.compare(f.name) == 0; }); + if (it != creators_.cend() && (*it).is_available()) { + return (*it).instantiate(std::forward(args)...); + } + return nullptr; + } else { + for (const auto& creator : creators_) { + if (!creator.is_available()) continue; + auto instance = creator.instantiate(std::forward(args)...); + if (!instance) continue; + return instance; + } + return nullptr; + } + } + + std::vector> CreateAll(const std::string_view name, + Args... args) { + std::vector> instances; + if (!name.empty() && name != "any") { + auto it = std::find_if( + creators_.cbegin(), creators_.cend(), + [&name](const auto& f) { return name.compare(f.name) == 0; }); + if (it != creators_.cend() && (*it).is_available()) { + auto instance = (*it).instantiate(std::forward(args)...); + if (instance) { + instances.emplace_back(std::move(instance)); + } + } + } else { + for (const auto& creator : creators_) { + if (!creator.is_available()) continue; + auto instance = creator.instantiate(std::forward(args)...); + if (instance) { + instances.emplace_back(std::move(instance)); + } + } + } + return instances; + } }; - std::vector creators_; + explicit EmulatorApp(xe::ui::WindowedAppContext& app_context); - public: - void Add(const std::string_view name, std::function is_available, - std::function(Args...)> instantiate) { - creators_.push_back({std::string(name), is_available, instantiate}); - } + static std::unique_ptr CreateAudioSystem( + cpu::Processor* processor); + static std::unique_ptr CreateGraphicsSystem(); + static std::vector> CreateInputDrivers( + ui::Window* window); - void Add(const std::string_view name, - std::function(Args...)> instantiate) { - auto always_available = []() { return true; }; - Add(name, always_available, instantiate); - } + void EmulatorThread(); + void ShutdownEmulatorThreadFromUIThread(); - template - void Add(const std::string_view name) { - Add(name, DT::IsAvailable, [](Args... args) { - return std::make_unique
(std::forward(args)...); - }); - } + std::unique_ptr emulator_; + std::unique_ptr emulator_window_; - std::unique_ptr Create(const std::string_view name, Args... args) { - if (!name.empty() && name != "any") { - auto it = std::find_if( - creators_.cbegin(), creators_.cend(), - [&name](const auto& f) { return name.compare(f.name) == 0; }); - if (it != creators_.cend() && (*it).is_available()) { - return (*it).instantiate(std::forward(args)...); - } - return nullptr; - } else { - for (const auto& creator : creators_) { - if (!creator.is_available()) continue; - auto instance = creator.instantiate(std::forward(args)...); - if (!instance) continue; - return instance; - } - return nullptr; - } - } + // Created on demand, used by the emulator. + std::unique_ptr debug_window_; - std::vector> CreateAll(const std::string_view name, - Args... args) { - std::vector> instances; - if (!name.empty() && name != "any") { - auto it = std::find_if( - creators_.cbegin(), creators_.cend(), - [&name](const auto& f) { return name.compare(f.name) == 0; }); - if (it != creators_.cend() && (*it).is_available()) { - auto instance = (*it).instantiate(std::forward(args)...); - if (instance) { - instances.emplace_back(std::move(instance)); - } - } - } else { - for (const auto& creator : creators_) { - if (!creator.is_available()) continue; - auto instance = creator.instantiate(std::forward(args)...); - if (instance) { - instances.emplace_back(std::move(instance)); - } - } - } - return instances; - } + // Refreshing the emulator - placed after its dependencies. + std::atomic emulator_thread_quit_requested_; + std::unique_ptr emulator_thread_event_; + std::thread emulator_thread_; }; -std::unique_ptr CreateAudioSystem(cpu::Processor* processor) { +EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context) + : xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]") { + AddPositionalOption("target"); +} + +EmulatorApp::~EmulatorApp() { + // Should be shut down from OnDestroy if OnInitialize has ever been done, but + // for the most safety as a running thread may be destroyed only after + // joining. + ShutdownEmulatorThreadFromUIThread(); +} + +std::unique_ptr EmulatorApp::CreateAudioSystem( + cpu::Processor* processor) { Factory factory; #if XE_PLATFORM_WIN32 factory.Add("xaudio2"); @@ -177,7 +238,7 @@ std::unique_ptr CreateAudioSystem(cpu::Processor* processor) { return factory.Create(cvars::apu, processor); } -std::unique_ptr CreateGraphicsSystem() { +std::unique_ptr EmulatorApp::CreateGraphicsSystem() { Factory factory; #if XE_PLATFORM_WIN32 factory.Add("d3d12"); @@ -187,7 +248,7 @@ std::unique_ptr CreateGraphicsSystem() { return factory.Create(cvars::gpu); } -std::vector> CreateInputDrivers( +std::vector> EmulatorApp::CreateInputDrivers( ui::Window* window) { std::vector> drivers; if (cvars::hid.compare("nop") == 0) { @@ -215,9 +276,9 @@ std::vector> CreateInputDrivers( return drivers; } -int xenia_main(const std::vector& args) { +bool EmulatorApp::OnInitialize() { Profiler::Initialize(); - Profiler::ThreadEnter("main"); + Profiler::ThreadEnter("Main"); // Figure out where internal files and content should go. std::filesystem::path storage_root = cvars::storage_root; @@ -275,20 +336,55 @@ int xenia_main(const std::vector& args) { } // Create the emulator but don't initialize so we can setup the window. - auto emulator = + emulator_ = std::make_unique("", storage_root, content_root, cache_root); // Main emulator display window. - auto emulator_window = EmulatorWindow::Create(emulator.get()); + emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context()); + if (!emulator_window_) { + XELOGE("Failed to create the main emulator window"); + return false; + } + + // Setup the emulator and run its loop in a separate thread. + emulator_thread_quit_requested_.store(false, std::memory_order_relaxed); + emulator_thread_event_ = xe::threading::Event::CreateAutoResetEvent(false); + emulator_thread_ = std::thread(&EmulatorApp::EmulatorThread, this); + + return true; +} + +void EmulatorApp::OnDestroy() { + ShutdownEmulatorThreadFromUIThread(); + + if (cvars::discord) { + discord::DiscordPresence::Shutdown(); + } + + Profiler::Dump(); + // The profiler needs to shut down before the graphics context. + Profiler::Shutdown(); + + // TODO(DrChat): Remove this code and do a proper exit. + XELOGI("Cheap-skate exit!"); + std::quick_exit(EXIT_SUCCESS); +} + +void EmulatorApp::EmulatorThread() { + assert_not_null(emulator_thread_event_); + + xe::threading::set_name("Emulator"); + Profiler::ThreadEnter("Emulator"); // 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(), CreateAudioSystem, - CreateGraphicsSystem, CreateInputDrivers); + emulator_->Setup(emulator_window_->window(), CreateAudioSystem, + CreateGraphicsSystem, CreateInputDrivers); if (XFAILED(result)) { XELOGE("Failed to setup emulator: {:08X}", result); - return 1; + app_context().RequestDeferredQuit(); + return; } if (cvars::mount_scratch) { @@ -297,10 +393,11 @@ int xenia_main(const std::vector& args) { if (!scratch_device->Initialize()) { XELOGE("Unable to scan scratch path"); } else { - if (!emulator->file_system()->RegisterDevice(std::move(scratch_device))) { + if (!emulator_->file_system()->RegisterDevice( + std::move(scratch_device))) { XELOGE("Unable to register scratch path"); } else { - emulator->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH"); + emulator_->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH"); } } } @@ -311,10 +408,10 @@ int xenia_main(const std::vector& args) { if (!cache0_device->Initialize()) { XELOGE("Unable to scan cache0 path"); } else { - if (!emulator->file_system()->RegisterDevice(std::move(cache0_device))) { + if (!emulator_->file_system()->RegisterDevice(std::move(cache0_device))) { XELOGE("Unable to register cache0 path"); } else { - emulator->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0"); + emulator_->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0"); } } @@ -323,10 +420,10 @@ int xenia_main(const std::vector& args) { if (!cache1_device->Initialize()) { XELOGE("Unable to scan cache1 path"); } else { - if (!emulator->file_system()->RegisterDevice(std::move(cache1_device))) { + if (!emulator_->file_system()->RegisterDevice(std::move(cache1_device))) { XELOGE("Unable to register cache1 path"); } else { - emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1"); + emulator_->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1"); } } @@ -339,79 +436,62 @@ int xenia_main(const std::vector& args) { if (!cache_device->Initialize()) { XELOGE("Unable to scan cache path"); } else { - if (!emulator->file_system()->RegisterDevice(std::move(cache_device))) { + if (!emulator_->file_system()->RegisterDevice(std::move(cache_device))) { XELOGE("Unable to register cache path"); } else { - emulator->file_system()->RegisterSymbolicLink("cache:", "\\CACHE"); + emulator_->file_system()->RegisterSymbolicLink("cache:", "\\CACHE"); } } } // Set a debug handler. // This will respond to debugging requests so we can open the debug UI. - std::unique_ptr debug_window; if (cvars::debug) { - emulator->processor()->set_debug_listener_request_handler( - [&](xe::cpu::Processor* processor) { - if (debug_window) { - return debug_window.get(); + emulator_->processor()->set_debug_listener_request_handler( + [this](xe::cpu::Processor* processor) { + if (debug_window_) { + return debug_window_.get(); } - emulator_window->loop()->PostSynchronous([&]() { - debug_window = xe::debug::ui::DebugWindow::Create( - emulator.get(), emulator_window->loop()); - debug_window->window()->on_closed.AddListener( - [&](xe::ui::UIEvent* e) { - emulator->processor()->set_debug_listener(nullptr); - emulator_window->loop()->Post( - [&]() { debug_window.reset(); }); + app_context().CallInUIThreadSynchronous([this]() { + debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(), + app_context()); + debug_window_->window()->on_closed.AddListener( + [this](xe::ui::UIEvent* e) { + emulator_->processor()->set_debug_listener(nullptr); + app_context().CallInUIThread( + [this]() { debug_window_.reset(); }); }); }); - return debug_window.get(); + // If failed to enqueue the UI thread call, this will just be null. + return debug_window_.get(); }); } - auto evt = xe::threading::Event::CreateAutoResetEvent(false); - emulator->on_launch.AddListener([&](auto title_id, const auto& game_title) { + emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) { if (cvars::discord) { discord::DiscordPresence::PlayingTitle( game_title.empty() ? "Unknown Title" : std::string(game_title)); } - emulator_window->UpdateTitle(); - evt->Set(); + app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); }); + emulator_thread_event_->Set(); }); - emulator->on_shader_storage_initialization.AddListener( - [&](bool initializing) { - emulator_window->SetInitializingShaderStorage(initializing); + emulator_->on_shader_storage_initialization.AddListener( + [this](bool initializing) { + app_context().CallInUIThread([this, initializing]() { + emulator_window_->SetInitializingShaderStorage(initializing); + }); }); - emulator->on_terminate.AddListener([&]() { + emulator_->on_terminate.AddListener([]() { if (cvars::discord) { discord::DiscordPresence::NotPlaying(); } }); - emulator_window->window()->on_closing.AddListener([&](ui::UIEvent* e) { - // This needs to shut down before the graphics context. - Profiler::Shutdown(); - }); - - bool exiting = false; - emulator_window->loop()->on_quit.AddListener([&](ui::UIEvent* e) { - exiting = true; - evt->Set(); - - if (cvars::discord) { - discord::DiscordPresence::Shutdown(); - } - - // TODO(DrChat): Remove this code and do a proper exit. - XELOGI("Cheap-skate exit!"); - exit(0); - }); - // Enable the main menu now that the emulator is properly loaded - emulator_window->window()->EnableMainMenu(); + app_context().CallInUIThread( + [this]() { emulator_window_->window()->EnableMainMenu(); }); // Grab path from the flag or unnamed argument. std::filesystem::path path; @@ -420,50 +500,56 @@ int xenia_main(const std::vector& args) { } // Toggles fullscreen - if (cvars::fullscreen) emulator_window->ToggleFullscreen(); + if (cvars::fullscreen) { + app_context().CallInUIThread( + [this]() { emulator_window_->ToggleFullscreen(); }); + } if (!path.empty()) { // Normalize the path and make absolute. auto abs_path = std::filesystem::absolute(path); - result = emulator->LaunchPath(abs_path); + result = emulator_->LaunchPath(abs_path); if (XFAILED(result)) { xe::FatalError(fmt::format("Failed to launch target: {:08X}", result)); - emulator.reset(); - emulator_window.reset(); - return 1; + app_context().RequestDeferredQuit(); + return; } } - // Now, we're going to use the main thread to drive events related to - // emulation. - while (!exiting) { - xe::threading::Wait(evt.get(), false); - + // Now, we're going to use this thread to drive events related to emulation. + while (!emulator_thread_quit_requested_.load(std::memory_order_relaxed)) { + xe::threading::Wait(emulator_thread_event_.get(), false); while (true) { - emulator->WaitUntilExit(); - if (emulator->TitleRequested()) { - emulator->LaunchNextTitle(); + emulator_->WaitUntilExit(); + if (emulator_->TitleRequested()) { + emulator_->LaunchNextTitle(); } else { break; } } } +} - debug_window.reset(); - emulator.reset(); - - if (cvars::discord) { - discord::DiscordPresence::Shutdown(); +void EmulatorApp::ShutdownEmulatorThreadFromUIThread() { + // TODO(Triang3l): Proper shutdown of the emulator (relying on std::quick_exit + // for now) - currently WaitUntilExit loops forever otherwise (plus possibly + // lots of other things not shutting down correctly now). Some parts of the + // code call the regular std::exit, which seems to be calling destructors (at + // least on Linux), so the entire join is currently commented out. +#if 0 + // Same thread as the one created it, to make sure there's zero possibility of + // a race with the creation of the emulator thread. + assert_true(app_context().IsInUIThread()); + emulator_thread_quit_requested_.store(true, std::memory_order_relaxed); + if (!emulator_thread_.joinable()) { + return; } - - Profiler::Dump(); - Profiler::Shutdown(); - emulator_window.reset(); - return 0; + emulator_thread_event_->Set(); + emulator_thread_.join(); +#endif } } // namespace app } // namespace xe -DEFINE_ENTRY_POINT("xenia", xe::app::xenia_main, "[Path to .iso/.xex]", - "target"); +XE_DEFINE_WINDOWED_APP(xenia, xe::app::EmulatorApp::Create); diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc b/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc index f393706c9..00ed1a882 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc +++ b/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc @@ -9,13 +9,14 @@ #include "xenia/apu/xaudio2/xaudio2_audio_driver.h" -// Must be included before xaudio2.h so we get the right windows.h include. -#include "xenia/base/platform_win.h" - #include "xenia/apu/apu_flags.h" #include "xenia/apu/conversion.h" +#include "xenia/apu/xaudio2/xaudio2_api.h" +#include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" +#include "xenia/base/platform_win.h" +#include "xenia/base/threading.h" namespace xe { namespace apu { @@ -73,11 +74,6 @@ bool XAudio2AudioDriver::Initialize() { } } - // First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too - // much time from other things. Ideally should process audio on what roughly - // represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or - // even beyond the 6 guest cores. - api::XAUDIO2_PROCESSOR processor = 0x00000001; if (api_minor_version_ >= 8) { union { // clang-format off @@ -94,7 +90,7 @@ bool XAudio2AudioDriver::Initialize() { assert_always(); return false; } - hr = xaudio2_create(&objects_.api_2_8.audio, 0, processor); + hr = xaudio2_create(&objects_.api_2_8.audio, 0, kProcessor); if (FAILED(hr)) { XELOGE("XAudio2Create failed with {:08X}", hr); assert_always(); @@ -102,20 +98,38 @@ bool XAudio2AudioDriver::Initialize() { } return InitializeObjects(objects_.api_2_8); } else { - hr = CoCreateInstance(__uuidof(api::XAudio2_7), NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&objects_.api_2_7.audio)); - if (FAILED(hr)) { - XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr); - assert_always(); + // We need to be able to accept frames from any non-STA thread - primarily + // from any guest thread, so MTA needs to be used. The AudioDriver, however, + // may be initialized from the UI thread, which has the STA concurrency + // model, or from another thread regardless of its concurrency model. So, + // all management of the objects needs to be performed in MTA. Launch the + // lifecycle management thread, which will handle initialization and + // shutdown, and also provide a scope for implicit MTA in threads that have + // never initialized COM explicitly, which lasts until all threads that have + // initialized MTA explicitly have uninitialized it - the thread that holds + // the MTA scope needs to be running while other threads are able to submit + // frames as they might have not initialized MTA explicitly. + // https://devblogs.microsoft.com/oldnewthing/?p=4613 + assert_false(mta_thread_.joinable()); + mta_thread_initialization_attempt_completed_ = false; + mta_thread_shutdown_requested_ = false; + mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this); + { + std::unique_lock mta_thread_initialization_completion_lock( + mta_thread_initialization_completion_mutex_); + while (true) { + if (mta_thread_initialization_attempt_completed_) { + break; + } + mta_thread_initialization_completion_cond_.wait( + mta_thread_initialization_completion_lock); + } + } + if (!mta_thread_initialization_completion_result_) { + mta_thread_.join(); return false; } - hr = objects_.api_2_7.audio->Initialize(0, processor); - if (FAILED(hr)) { - XELOGE("IXAudio2::Initialize failed with {:08X}", hr); - assert_always(); - return false; - } - return InitializeObjects(objects_.api_2_7); + return true; } } @@ -249,7 +263,16 @@ void XAudio2AudioDriver::Shutdown() { if (api_minor_version_ >= 8) { ShutdownObjects(objects_.api_2_8); } else { - ShutdownObjects(objects_.api_2_7); + // XAudio 2.7 lifecycle is managed by the MTA thread. + if (mta_thread_.joinable()) { + { + std::unique_lock mta_thread_shutdown_request_lock( + mta_thread_shutdown_request_mutex_); + mta_thread_shutdown_requested_ = true; + } + mta_thread_shutdown_request_cond_.notify_all(); + mta_thread_.join(); + } } if (xaudio2_module_) { @@ -283,6 +306,82 @@ void XAudio2AudioDriver::ShutdownObjects(Objects& objects) { } } +void XAudio2AudioDriver::MTAThread() { + xe::threading::set_name("XAudio 2.7 MTA"); + + assert_false(mta_thread_initialization_attempt_completed_); + + bool initialized = false; + + // Initializing MTA COM in this thread, as well making other (guest) threads + // that don't explicitly call CoInitializeEx implicitly MTA for the period of + // time when they can interact with XAudio 2.7 through the XAudio2AudioDriver, + // until the CoUninitialize (to be more precise, the CoUninitialize for the + // last remaining MTA thread, but we need implicit MTA for the audio here). + // https://devblogs.microsoft.com/oldnewthing/?p=4613 + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + XELOGE("XAudio 2.7 MTA thread CoInitializeEx failed with {:08X}", hr); + } else { + hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&objects_.api_2_7.audio)); + if (FAILED(hr)) { + XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr); + } else { + hr = objects_.api_2_7.audio->Initialize(0, kProcessor); + if (FAILED(hr)) { + XELOGE("IXAudio2::Initialize failed with {:08X}", hr); + } else { + if (InitializeObjects(objects_.api_2_7)) { + initialized = true; + + // Initialized successfully, await a shutdown request while keeping an + // implicit COM MTA scope. + + mta_thread_initialization_completion_result_ = true; + { + std::unique_lock + mta_thread_initialization_completion_lock( + mta_thread_initialization_completion_mutex_); + mta_thread_initialization_attempt_completed_ = true; + } + mta_thread_initialization_completion_cond_.notify_all(); + + { + std::unique_lock mta_thread_shutdown_request_lock( + mta_thread_shutdown_request_mutex_); + while (true) { + if (mta_thread_shutdown_requested_) { + break; + } + mta_thread_shutdown_request_cond_.wait( + mta_thread_shutdown_request_lock); + } + } + } + + // Even if InitializeObjects has failed, need to clean up with + // ShutdownObjects. + ShutdownObjects(objects_.api_2_7); + } + } + CoUninitialize(); + } + + if (!initialized) { + mta_thread_initialization_completion_result_ = false; + { + // Failed to initialize - notify the threads waiting for the + // initialization. + std::unique_lock mta_thread_initialization_completion_lock( + mta_thread_initialization_completion_mutex_); + mta_thread_initialization_attempt_completed_ = true; + } + mta_thread_initialization_completion_cond_.notify_all(); + } +} + } // namespace xaudio2 } // namespace apu } // namespace xe diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_driver.h b/src/xenia/apu/xaudio2/xaudio2_audio_driver.h index 42d4b59a9..accfdd56a 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_driver.h +++ b/src/xenia/apu/xaudio2/xaudio2_audio_driver.h @@ -10,8 +10,13 @@ #ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_ #define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_ +#include +#include +#include + #include "xenia/apu/audio_driver.h" #include "xenia/apu/xaudio2/xaudio2_api.h" +#include "xenia/base/platform.h" #include "xenia/base/threading.h" struct IXAudio2; @@ -28,17 +33,45 @@ class XAudio2AudioDriver : public AudioDriver { ~XAudio2AudioDriver() override; bool Initialize(); + // Must not be called from COM STA threads as MTA XAudio 2.7 may be used. It's + // fine to call this from threads that have never initialized COM as + // initializing MTA for any thread implicitly initializes it for all threads + // not explicitly requesting STA (until COM is uninitialized all threads that + // have initialized MTA). + // https://devblogs.microsoft.com/oldnewthing/?p=4613 void SubmitFrame(uint32_t frame_ptr) override; void Shutdown(); private: + // First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too + // much time from other things. Ideally should process audio on what roughly + // represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or + // even beyond the 6 guest cores. + api::XAUDIO2_PROCESSOR kProcessor = 0x00000001; + + // For XAudio 2.7, InitializeObjects and ShutdownObjects must be called only + // in the lifecycle management thread with COM MTA initialized. template bool InitializeObjects(Objects& objects); template void ShutdownObjects(Objects& objects); + void MTAThread(); + void* xaudio2_module_ = nullptr; uint32_t api_minor_version_ = 7; + + bool mta_thread_initialization_completion_result_; + std::mutex mta_thread_initialization_completion_mutex_; + std::condition_variable mta_thread_initialization_completion_cond_; + bool mta_thread_initialization_attempt_completed_; + + std::mutex mta_thread_shutdown_request_mutex_; + std::condition_variable mta_thread_shutdown_request_cond_; + bool mta_thread_shutdown_requested_; + + std::thread mta_thread_; + union { struct { api::IXAudio2_7* audio; diff --git a/src/xenia/base/app_win32.manifest b/src/xenia/base/app_win32.manifest new file mode 100644 index 000000000..acba0ff0e --- /dev/null +++ b/src/xenia/base/app_win32.manifest @@ -0,0 +1,14 @@ + + + + + + + + + + True/PM + PerMonitor + + + diff --git a/src/xenia/base/console.h b/src/xenia/base/console.h new file mode 100644 index 000000000..8f7f26728 --- /dev/null +++ b/src/xenia/base/console.h @@ -0,0 +1,22 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_CONSOLE_H_ +#define XENIA_BASE_CONSOLE_H_ + +namespace xe { + +// Returns true if there is a user-visible console attached to receive stdout. +bool has_console_attached(); + +void AttachConsole(); + +} // namespace xe + +#endif // XENIA_BASE_CONSOLE_H_ diff --git a/src/xenia/base/main.h b/src/xenia/base/console_app_main.h similarity index 51% rename from src/xenia/base/main.h rename to src/xenia/base/console_app_main.h index 324abfe71..1db83cf60 100644 --- a/src/xenia/base/main.h +++ b/src/xenia/base/console_app_main.h @@ -2,54 +2,50 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ -#ifndef XENIA_BASE_MAIN_H_ -#define XENIA_BASE_MAIN_H_ +#ifndef XENIA_BASE_CONSOLE_APP_MAIN_H_ +#define XENIA_BASE_CONSOLE_APP_MAIN_H_ -#include #include #include -#include "xenia/base/cvar.h" -#include "xenia/base/platform.h" - namespace xe { -// Returns true if there is a user-visible console attached to receive stdout. -bool has_console_attached(); - -void AttachConsole(); - // Extern defined by user code. This must be present for the application to // launch. -struct EntryInfo { +struct ConsoleAppEntryInfo { std::string name; int (*entry_point)(const std::vector& args); bool transparent_options; // no argument parsing - std::optional positional_usage; - std::optional> positional_options; + std::string positional_usage; + std::vector positional_options; }; -EntryInfo GetEntryInfo(); +ConsoleAppEntryInfo GetConsoleAppEntryInfo(); -#define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \ - xe::EntryInfo xe::GetEntryInfo() { \ +// TODO(Triang3l): Multiple console app entry points on Android when a console +// activity is added. This is currently for individual executables running on +// Termux. + +#define XE_DEFINE_CONSOLE_APP(name, entry_point, positional_usage, ...) \ + xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \ std::initializer_list positional_options = {__VA_ARGS__}; \ - return xe::EntryInfo{ \ + return xe::ConsoleAppEntryInfo{ \ name, entry_point, false, positional_usage, \ std::vector(std::move(positional_options))}; \ } // TODO(Joel Linn): Add some way to filter consumed arguments in // cvar::ParseLaunchArguments() -#define DEFINE_ENTRY_POINT_TRANSPARENT(name, entry_point) \ - xe::EntryInfo xe::GetEntryInfo() { \ - return xe::EntryInfo{name, entry_point, true, std::nullopt, std::nullopt}; \ +#define XE_DEFINE_CONSOLE_APP_TRANSPARENT(name, entry_point) \ + xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \ + return xe::ConsoleAppEntryInfo{name, entry_point, true, std::string(), \ + std::vector()}; \ } } // namespace xe -#endif // XENIA_BASE_MAIN_H_ +#endif // XENIA_BASE_CONSOLE_APP_MAIN_H_ diff --git a/src/xenia/base/console_app_main_android.cc b/src/xenia/base/console_app_main_android.cc new file mode 100644 index 000000000..70e69fc5e --- /dev/null +++ b/src/xenia/base/console_app_main_android.cc @@ -0,0 +1,10 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/base/console_app_posix.cc" diff --git a/src/xenia/base/main_posix.cc b/src/xenia/base/console_app_main_posix.cc similarity index 64% rename from src/xenia/base/main_posix.cc rename to src/xenia/base/console_app_main_posix.cc index d3f0ef403..29a3ae6ab 100644 --- a/src/xenia/base/main_posix.cc +++ b/src/xenia/base/console_app_main_posix.cc @@ -7,42 +7,32 @@ ****************************************************************************** */ -#include -#include +#include +#include +#include "xenia/base/console_app_main.h" #include "xenia/base/cvar.h" -#include "xenia/base/main.h" - -#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" -#include "xenia/base/string.h" - -namespace xe { - -bool has_console_attached() { return isatty(fileno(stdin)) == 1; } - -void AttachConsole() {} - -} // namespace xe extern "C" int main(int argc, char** argv) { - auto entry_info = xe::GetEntryInfo(); + xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo(); if (!entry_info.transparent_options) { - cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(), - entry_info.positional_options.value()); + cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage, + entry_info.positional_options); } + // Initialize logging. Needs parsed cvars. + xe::InitializeLogging(entry_info.name); + std::vector args; for (int n = 0; n < argc; n++) { - args.push_back(argv[n]); + args.emplace_back(argv[n]); } - // Initialize logging. Needs parsed FLAGS. - xe::InitializeLogging(entry_info.name); - - // Call app-provided entry point. int result = entry_info.entry_point(args); + xe::ShutdownLogging(); + return result; } diff --git a/src/xenia/base/console_app_main_win.cc b/src/xenia/base/console_app_main_win.cc new file mode 100644 index 000000000..cd2a38dc4 --- /dev/null +++ b/src/xenia/base/console_app_main_win.cc @@ -0,0 +1,36 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include + +#include "xenia/base/console_app_main.h" +#include "xenia/base/main_win.h" + +int main(int argc_ignored, char** argv_ignored) { + xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo(); + + std::vector args; + if (!xe::ParseWin32LaunchArguments(entry_info.transparent_options, + entry_info.positional_usage, + entry_info.positional_options, &args)) { + return EXIT_FAILURE; + } + + int result = xe::InitializeWin32App(entry_info.name); + if (result) { + return result; + } + + result = entry_info.entry_point(args); + + xe::ShutdownWin32App(); + + return result; +} diff --git a/src/xenia/base/console_posix.cc b/src/xenia/base/console_posix.cc new file mode 100644 index 000000000..0f8cf38f9 --- /dev/null +++ b/src/xenia/base/console_posix.cc @@ -0,0 +1,21 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include + +#include "xenia/base/console.h" + +namespace xe { + +bool has_console_attached() { return isatty(fileno(stdin)) == 1; } + +void AttachConsole() {} + +} // namespace xe diff --git a/src/xenia/base/console_win.cc b/src/xenia/base/console_win.cc new file mode 100644 index 000000000..e6b8fb887 --- /dev/null +++ b/src/xenia/base/console_win.cc @@ -0,0 +1,60 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include +#include +#include + +#include "xenia/base/console.h" +#include "xenia/base/platform_win.h" + +namespace xe { + +// TODO(Triang3l): Set the default depending on the actual subsystem. Currently +// it inhibits message boxes. +static bool has_console_attached_ = true; + +bool has_console_attached() { return has_console_attached_; } + +static bool has_shell_environment_variable() { + size_t size = 0; + // Check if SHELL exists + // If it doesn't, then we are in a Windows Terminal + auto error = getenv_s(&size, nullptr, 0, "SHELL"); + if (error) { + return false; + } + return !!size; +} + +void AttachConsole() { + bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE; + if (!has_console || !has_shell_environment_variable()) { + // We weren't launched from a console, so just return. + has_console_attached_ = false; + return; + } + + AllocConsole(); + + has_console_attached_ = true; + + auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE); + auto con_handle = _open_osfhandle(std_handle, _O_TEXT); + auto fp = _fdopen(con_handle, "w"); + freopen_s(&fp, "CONOUT$", "w", stdout); + + std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE); + con_handle = _open_osfhandle(std_handle, _O_TEXT); + fp = _fdopen(con_handle, "w"); + freopen_s(&fp, "CONOUT$", "w", stderr); +} + +} // namespace xe diff --git a/src/xenia/base/cvar.cc b/src/xenia/base/cvar.cc index cd85c7a68..95c7a58f3 100644 --- a/src/xenia/base/cvar.cc +++ b/src/xenia/base/cvar.cc @@ -14,8 +14,8 @@ #define UTF_CPP_CPLUSPLUS 201703L #include "third_party/utfcpp/source/utf8.h" +#include "xenia/base/console.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/system.h" namespace utfcpp = utf8; diff --git a/src/xenia/base/logging.cc b/src/xenia/base/logging.cc index 02fa787a4..b78ad88e3 100644 --- a/src/xenia/base/logging.cc +++ b/src/xenia/base/logging.cc @@ -18,10 +18,10 @@ #include "third_party/disruptorplus/include/disruptorplus/sequence_barrier.hpp" #include "third_party/disruptorplus/include/disruptorplus/spin_wait_strategy.hpp" #include "xenia/base/atomic.h" +#include "xenia/base/console.h" #include "xenia/base/cvar.h" #include "xenia/base/debugging.h" #include "xenia/base/filesystem.h" -#include "xenia/base/main.h" #include "xenia/base/math.h" #include "xenia/base/memory.h" #include "xenia/base/ring_buffer.h" diff --git a/src/xenia/base/main_init_android.cc b/src/xenia/base/main_init_android.cc new file mode 100644 index 000000000..28b976731 --- /dev/null +++ b/src/xenia/base/main_init_android.cc @@ -0,0 +1,10 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +// Nothing. Stub. diff --git a/src/xenia/base/main_init_posix.cc b/src/xenia/base/main_init_posix.cc index 4a426a196..28b976731 100644 --- a/src/xenia/base/main_init_posix.cc +++ b/src/xenia/base/main_init_posix.cc @@ -2,13 +2,9 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ -#include "xenia/base/main.h" - -#include - // Nothing. Stub. diff --git a/src/xenia/base/main_init_win.cc b/src/xenia/base/main_init_win.cc index e6b0c9c59..6b0a9059a 100644 --- a/src/xenia/base/main_init_win.cc +++ b/src/xenia/base/main_init_win.cc @@ -7,10 +7,11 @@ ****************************************************************************** */ -#include "xenia/base/main.h" +#include "xenia/base/platform_win.h" #include +// Includes Windows headers, so it goes after platform_win.h. #include "third_party/xbyak/xbyak/xbyak_util.h" class StartupAvxCheck { diff --git a/src/xenia/base/main_win.cc b/src/xenia/base/main_win.cc index 5d68811f1..33bd65e75 100644 --- a/src/xenia/base/main_win.cc +++ b/src/xenia/base/main_win.cc @@ -7,14 +7,12 @@ ****************************************************************************** */ -#include -#include - -#include +#include +#include #include "xenia/base/cvar.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" +#include "xenia/base/main_win.h" #include "xenia/base/platform_win.h" #include "xenia/base/string.h" @@ -24,54 +22,11 @@ // For RequestHighPerformance. #include -// Includes Windows headers, so it goes here. -#include "third_party/xbyak/xbyak/xbyak_util.h" - DEFINE_bool(win32_high_freq, true, "Requests high performance from the NT kernel", "Kernel"); -DEFINE_bool(enable_console, false, "Open a console window with the main window", - "General"); namespace xe { -bool has_console_attached_ = true; - -bool has_console_attached() { return has_console_attached_; } - -bool has_shell_environment_variable() { - size_t size = 0; - // Check if SHELL exists - // If it doesn't, then we are in a Windows Terminal - auto error = getenv_s(&size, nullptr, 0, "SHELL"); - if (error) { - return false; - } - return !!size; -} - -void AttachConsole() { - bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE; - if (!has_console || !has_shell_environment_variable()) { - // We weren't launched from a console, so just return. - has_console_attached_ = false; - return; - } - - AllocConsole(); - - has_console_attached_ = true; - - auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE); - auto con_handle = _open_osfhandle(std_handle, _O_TEXT); - auto fp = _fdopen(con_handle, "w"); - freopen_s(&fp, "CONOUT$", "w", stdout); - - std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE); - con_handle = _open_osfhandle(std_handle, _O_TEXT); - fp = _fdopen(con_handle, "w"); - freopen_s(&fp, "CONOUT$", "w", stderr); -} - static void RequestHighPerformance() { #if XE_PLATFORM_WIN32 NTSTATUS(*NtQueryTimerResolution) @@ -97,8 +52,10 @@ static void RequestHighPerformance() { #endif } -static bool parse_launch_arguments(const xe::EntryInfo& entry_info, - std::vector& args) { +bool ParseWin32LaunchArguments( + bool transparent_options, const std::string_view positional_usage, + const std::vector& positional_options, + std::vector* args_out) { auto command_line = GetCommandLineW(); int wargc; @@ -118,48 +75,24 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info, LocalFree(wargv); - if (!entry_info.transparent_options) { - cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(), - entry_info.positional_options.value()); + if (!transparent_options) { + cvar::ParseLaunchArguments(argc, argv, positional_usage, + positional_options); } - args.clear(); - for (int n = 0; n < argc; n++) { - args.push_back(std::string(argv[n])); + if (args_out) { + args_out->clear(); + for (int n = 0; n < argc; n++) { + args_out->push_back(std::string(argv[n])); + } } return true; } -int Main() { - auto entry_info = xe::GetEntryInfo(); - - std::vector args; - if (!parse_launch_arguments(entry_info, args)) { - return 1; - } - - // Attach a console so we can write output to stdout. If the user hasn't - // redirected output themselves it'll pop up a window. - if (cvars::enable_console) { - xe::AttachConsole(); - } - - // Setup COM on the main thread. - // NOTE: this may fail if COM has already been initialized - that's OK. -#pragma warning(suppress : 6031) - CoInitializeEx(nullptr, COINIT_MULTITHREADED); - +int InitializeWin32App(const std::string_view app_name) { // Initialize logging. Needs parsed FLAGS. - xe::InitializeLogging(entry_info.name); - - Xbyak::util::Cpu cpu; - if (!cpu.has(Xbyak::util::Cpu::tAVX)) { - xe::FatalError( - "Your CPU does not support AVX, which is required by Xenia. See the " - "FAQ for system requirements at https://xenia.jp"); - return -1; - } + xe::InitializeLogging(app_name); // Print version info. XELOGI( @@ -175,38 +108,9 @@ int Main() { RequestHighPerformance(); } - // Call app-provided entry point. - int result = entry_info.entry_point(args); - - xe::ShutdownLogging(); - return result; + return 0; } +void ShutdownWin32App() { xe::ShutdownLogging(); } + } // namespace xe - -// Used in console mode apps; automatically picked based on subsystem. -int main(int argc_ignored, char** argv_ignored) { return xe::Main(); } - -// Used in windowed apps; automatically picked based on subsystem. -int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) { - // Run normal entry point. - return xe::Main(); -} - -#if defined _M_IX86 -#pragma comment( \ - linker, \ - "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length) -#elif defined _M_IA64 -#pragma comment( \ - linker, \ - "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length) -#elif defined _M_X64 -#pragma comment( \ - linker, \ - "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length) -#else -#pragma comment( \ - linker, \ - "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length) -#endif diff --git a/src/xenia/base/main_win.h b/src/xenia/base/main_win.h new file mode 100644 index 000000000..87a07b272 --- /dev/null +++ b/src/xenia/base/main_win.h @@ -0,0 +1,29 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_MAIN_WIN_H_ +#define XENIA_BASE_MAIN_WIN_H_ + +#include +#include + +namespace xe { + +// Functions for calling in both windowed and console entry points. +bool ParseWin32LaunchArguments( + bool transparent_options, const std::string_view positional_usage, + const std::vector& positional_options, + std::vector* args_out); +// InitializeWin32App uses cvars, call ParseWin32LaunchArguments before. +int InitializeWin32App(const std::string_view app_name); +void ShutdownWin32App(); + +} // namespace xe + +#endif // XENIA_BASE_MAIN_WIN_H_ diff --git a/src/xenia/base/premake5.lua b/src/xenia/base/premake5.lua index f58bf5558..d53c34351 100644 --- a/src/xenia/base/premake5.lua +++ b/src/xenia/base/premake5.lua @@ -11,7 +11,8 @@ project("xenia-base") defines({ }) local_platform_files() - removefiles({"main_*.cc"}) + removefiles({"console_app_main_*.cc"}) + removefiles({"main_init_*.cc"}) files({ "debug_visualizers.natvis", }) diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc index 1d115af1e..b2da8aff7 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc @@ -7,10 +7,10 @@ ****************************************************************************** */ +#include "xenia/base/console_app_main.h" #include "xenia/base/cvar.h" #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/math.h" #include "xenia/base/platform.h" #include "xenia/base/string_buffer.h" @@ -483,5 +483,5 @@ int main(const std::vector& args) { } // namespace cpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]", - "test_name"); +XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]", + "test_name"); diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc index 074552d36..689544316 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_native_main.cc @@ -7,6 +7,8 @@ ****************************************************************************** */ +#include "xenia/base/console_app_main.h" +#include "xenia/base/cvar.h" #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/main.h" @@ -23,6 +25,7 @@ DEFINE_string(test_path, "src/xenia/cpu/ppc/testing/", "Directory scanned for test files."); DEFINE_string(test_bin_path, "src/xenia/cpu/ppc/testing/bin/", "Directory with binary outputs of the test files."); +DEFINE_transient_string(test_name, "", "Test suite name.", "General"); extern "C" void xe_call_native(void* context, void* fn); @@ -505,19 +508,13 @@ bool RunTests(const std::wstring& test_name) { return failed_count ? false : true; } -int main(const std::vector& args) { - // Grab test name, if present. - std::wstring test_name; - if (args.size() >= 2) { - test_name = args[1]; - } - - return RunTests(test_name) ? 0 : 1; +int main(const std::vector& args) { + return RunTests(cvars::test_name) ? 0 : 1; } } // namespace test } // namespace cpu } // namespace xe -DEFINE_ENTRY_POINT(L"xenia-cpu-ppc-test", L"xenia-cpu-ppc-test [test name]", - xe::cpu::test::main); +XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]", + "test_name"); diff --git a/src/xenia/cpu/ppc/testing/premake5.lua b/src/xenia/cpu/ppc/testing/premake5.lua index b9c007b27..d91256460 100644 --- a/src/xenia/cpu/ppc/testing/premake5.lua +++ b/src/xenia/cpu/ppc/testing/premake5.lua @@ -17,7 +17,7 @@ project("xenia-cpu-ppc-tests") }) files({ "ppc_testing_main.cc", - "../../../base/main_"..platform_suffix..".cc", + "../../../base/console_app_main_"..platform_suffix..".cc", }) files({ "*.s", @@ -46,7 +46,7 @@ project("xenia-cpu-ppc-nativetests") }) files({ "ppc_testing_native_main.cc", - "../../../base/main_"..platform_suffix..".cc", + "../../../base/console_app_main_"..platform_suffix..".cc", }) files({ "instr_*.s", diff --git a/src/xenia/cpu/testing/sandbox_main.cc b/src/xenia/cpu/testing/sandbox_main.cc index 0998ddefe..a126c1677 100644 --- a/src/xenia/cpu/testing/sandbox_main.cc +++ b/src/xenia/cpu/testing/sandbox_main.cc @@ -7,7 +7,7 @@ ****************************************************************************** */ -#include "xenia/base/main.h" +#include "xenia/base/console_app_main.h" #include "xenia/cpu/backend/x64/x64_backend.h" #include "xenia/cpu/cpu.h" #include "xenia/cpu/ppc/ppc_context.h" @@ -23,10 +23,10 @@ using xe::cpu::ppc::PPCContext; // TODO(benvanik): simple memory? move more into core? -int main(std::vector& args) { +int main(std::vector& args) { #if XE_OPTION_PROFILING xe::Profiler::Initialize(); - xe::Profiler::ThreadEnter("main"); + xe::Profiler::ThreadEnter("Main"); #endif // XE_OPTION_PROFILING size_t memory_size = 16 * 1024 * 1024; @@ -79,4 +79,4 @@ int main(std::vector& args) { } // namespace cpu } // namespace xe -DEFINE_ENTRY_POINT(L"xenia-cpu-sandbox", L"?", xe::cpu::sandbox::main); +XE_DEFINE_CONSOLE_APP("xenia-cpu-sandbox", xe::cpu::sandbox::main, ""); diff --git a/src/xenia/cpu/testing/util.h b/src/xenia/cpu/testing/util.h index 79ca8bc03..d5c89059c 100644 --- a/src/xenia/cpu/testing/util.h +++ b/src/xenia/cpu/testing/util.h @@ -12,7 +12,6 @@ #include -#include "xenia/base/main.h" #include "xenia/cpu/backend/x64/x64_backend.h" #include "xenia/cpu/hir/hir_builder.h" #include "xenia/cpu/ppc/ppc_context.h" diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc index 967646f9c..bb9ee8225 100644 --- a/src/xenia/debug/ui/debug_window.cc +++ b/src/xenia/debug/ui/debug_window.cc @@ -32,6 +32,7 @@ #include "xenia/kernel/xthread.h" #include "xenia/ui/graphics_provider.h" #include "xenia/ui/imgui_drawer.h" +#include "xenia/ui/windowed_app_context.h" DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI"); @@ -50,11 +51,12 @@ using xe::ui::UIEvent; const std::string kBaseTitle = "Xenia Debugger"; -DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop) +DebugWindow::DebugWindow(Emulator* emulator, + xe::ui::WindowedAppContext& app_context) : emulator_(emulator), processor_(emulator->processor()), - loop_(loop), - window_(xe::ui::Window::Create(loop_, kBaseTitle)) { + app_context_(app_context), + window_(xe::ui::Window::Create(app_context_, kBaseTitle)) { if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) { assert_always("Failed to initialize capstone"); } @@ -63,16 +65,18 @@ DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop) } DebugWindow::~DebugWindow() { - loop_->PostSynchronous([this]() { window_.reset(); }); + // Make sure pending functions referencing the DebugWindow are executed. + app_context_.ExecutePendingFunctionsFromUIThread(); if (capstone_handle_) { cs_close(&capstone_handle_); } } -std::unique_ptr DebugWindow::Create(Emulator* emulator, - xe::ui::Loop* loop) { - std::unique_ptr debug_window(new DebugWindow(emulator, loop)); +std::unique_ptr DebugWindow::Create( + Emulator* emulator, xe::ui::WindowedAppContext& app_context) { + std::unique_ptr debug_window( + new DebugWindow(emulator, app_context)); if (!debug_window->Initialize()) { xe::FatalError("Failed to initialize debug window"); return nullptr; @@ -87,8 +91,6 @@ bool DebugWindow::Initialize() { return false; } - loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); }); - // Main menu. auto main_menu = MenuItem::Create(MenuItem::Type::kNormal); auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File"); @@ -1425,7 +1427,7 @@ void DebugWindow::UpdateCache() { auto kernel_state = emulator_->kernel_state(); auto object_table = kernel_state->object_table(); - loop_->Post([this]() { + app_context_.CallInUIThread([this]() { std::string title = kBaseTitle; switch (processor_->execution_state()) { case cpu::ExecutionState::kEnded: @@ -1531,9 +1533,7 @@ Breakpoint* DebugWindow::LookupBreakpointAtAddress( } } -void DebugWindow::OnFocus() { - loop_->Post([this]() { window_->set_focus(true); }); -} +void DebugWindow::OnFocus() { Focus(); } void DebugWindow::OnDetached() { UpdateCache(); @@ -1546,30 +1546,34 @@ void DebugWindow::OnDetached() { void DebugWindow::OnExecutionPaused() { UpdateCache(); - loop_->Post([this]() { window_->set_focus(true); }); + Focus(); } void DebugWindow::OnExecutionContinued() { UpdateCache(); - loop_->Post([this]() { window_->set_focus(true); }); + Focus(); } void DebugWindow::OnExecutionEnded() { UpdateCache(); - loop_->Post([this]() { window_->set_focus(true); }); + Focus(); } void DebugWindow::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) { UpdateCache(); SelectThreadStackFrame(thread_info, 0, true); - loop_->Post([this]() { window_->set_focus(true); }); + Focus(); } void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint, cpu::ThreadDebugInfo* thread_info) { UpdateCache(); SelectThreadStackFrame(thread_info, 0, true); - loop_->Post([this]() { window_->set_focus(true); }); + Focus(); +} + +void DebugWindow::Focus() const { + app_context_.CallInUIThread([this]() { window_->set_focus(true); }); } } // namespace ui diff --git a/src/xenia/debug/ui/debug_window.h b/src/xenia/debug/ui/debug_window.h index 5a0a5844b..7c9cfae4f 100644 --- a/src/xenia/debug/ui/debug_window.h +++ b/src/xenia/debug/ui/debug_window.h @@ -18,9 +18,9 @@ #include "xenia/cpu/debug_listener.h" #include "xenia/cpu/processor.h" #include "xenia/emulator.h" -#include "xenia/ui/loop.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" namespace xe { @@ -31,11 +31,11 @@ class DebugWindow : public cpu::DebugListener { public: ~DebugWindow(); - static std::unique_ptr Create(Emulator* emulator, - xe::ui::Loop* loop); + static std::unique_ptr Create( + Emulator* emulator, xe::ui::WindowedAppContext& app_context); Emulator* emulator() const { return emulator_; } - xe::ui::Loop* loop() const { return loop_; } + xe::ui::WindowedAppContext& app_context() const { return app_context_; } xe::ui::Window* window() const { return window_.get(); } void OnFocus() override; @@ -48,7 +48,8 @@ class DebugWindow : public cpu::DebugListener { cpu::ThreadDebugInfo* thread_info) override; private: - explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop); + explicit DebugWindow(Emulator* emulator, + xe::ui::WindowedAppContext& app_context); bool Initialize(); void DrawFrame(); @@ -86,9 +87,11 @@ class DebugWindow : public cpu::DebugListener { cpu::Breakpoint* LookupBreakpointAtAddress( cpu::Breakpoint::AddressType address_type, uint64_t address); + void Focus() const; + Emulator* emulator_ = nullptr; cpu::Processor* processor_ = nullptr; - xe::ui::Loop* loop_ = nullptr; + xe::ui::WindowedAppContext& app_context_; std::unique_ptr window_; uint64_t last_draw_tick_count_ = 0; diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 92ddabe4f..7bb4109e4 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -40,6 +40,8 @@ #include "xenia/kernel/xboxkrnl/xboxkrnl_module.h" #include "xenia/memory.h" #include "xenia/ui/imgui_dialog.h" +#include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/vfs/devices/disc_image_device.h" #include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/null_device.h" @@ -233,7 +235,7 @@ X_STATUS Emulator::Setup( if (display_window_) { // Finish initializing the display. - display_window_->loop()->PostSynchronous([this]() { + display_window_->app_context().CallInUIThreadSynchronous([this]() { xe::ui::GraphicsContextLock context_lock(display_window_->context()); Profiler::set_window(display_window_); }); @@ -583,7 +585,7 @@ bool Emulator::ExceptionCallback(Exception* ex) { } // Display a dialog telling the user the guest has crashed. - display_window()->loop()->PostSynchronous([&]() { + display_window()->app_context().CallInUIThreadSynchronous([this]() { xe::ui::ImGuiDialog::ShowMessageBox( display_window(), "Uh-oh!", "The guest has crashed.\n\n" diff --git a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc index a0cc6fab6..411e95ce5 100644 --- a/src/xenia/gpu/d3d12/d3d12_graphics_system.cc +++ b/src/xenia/gpu/d3d12/d3d12_graphics_system.cc @@ -49,7 +49,7 @@ std::string D3D12GraphicsSystem::name() const { X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor, kernel::KernelState* kernel_state, ui::Window* target_window) { - provider_ = xe::ui::d3d12::D3D12Provider::Create(target_window); + provider_ = xe::ui::d3d12::D3D12Provider::Create(); auto d3d12_provider = static_cast(provider()); auto device = d3d12_provider->GetDevice(); diff --git a/src/xenia/gpu/d3d12/d3d12_trace_dump_main.cc b/src/xenia/gpu/d3d12/d3d12_trace_dump_main.cc index 127a2226f..eaa25ddd9 100644 --- a/src/xenia/gpu/d3d12/d3d12_trace_dump_main.cc +++ b/src/xenia/gpu/d3d12/d3d12_trace_dump_main.cc @@ -7,8 +7,8 @@ ****************************************************************************** */ +#include "xenia/base/console_app_main.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/gpu/d3d12/d3d12_command_processor.h" #include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/gpu/trace_dump.h" @@ -54,6 +54,6 @@ int trace_dump_main(const std::vector& args) { } // namespace gpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-dump", - xe::gpu::d3d12::trace_dump_main, "some.trace", - "target_trace_file"); +XE_DEFINE_CONSOLE_APP("xenia-gpu-d3d12-trace-dump", + xe::gpu::d3d12::trace_dump_main, "some.trace", + "target_trace_file"); diff --git a/src/xenia/gpu/d3d12/d3d12_trace_viewer_main.cc b/src/xenia/gpu/d3d12/d3d12_trace_viewer_main.cc index 481035d7c..71b3fc403 100644 --- a/src/xenia/gpu/d3d12/d3d12_trace_viewer_main.cc +++ b/src/xenia/gpu/d3d12/d3d12_trace_viewer_main.cc @@ -2,13 +2,15 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ +#include +#include + #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/gpu/d3d12/d3d12_command_processor.h" #include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/gpu/trace_viewer.h" @@ -17,10 +19,13 @@ namespace xe { namespace gpu { namespace d3d12 { -using namespace xe::gpu::xenos; - -class D3D12TraceViewer : public TraceViewer { +class D3D12TraceViewer final : public TraceViewer { public: + static std::unique_ptr Create( + xe::ui::WindowedAppContext& app_context) { + return std::unique_ptr(new D3D12TraceViewer(app_context)); + } + std::unique_ptr CreateGraphicsSystem() override { return std::unique_ptr(new D3D12GraphicsSystem()); } @@ -45,17 +50,15 @@ class D3D12TraceViewer : public TraceViewer { // TextureInfo/SamplerInfo which are going away. return 0; } -}; -int trace_viewer_main(const std::vector& args) { - D3D12TraceViewer trace_viewer; - return trace_viewer.Main(args); -} + private: + explicit D3D12TraceViewer(xe::ui::WindowedAppContext& app_context) + : TraceViewer(app_context, "xenia-gpu-d3d12-trace-viewer") {} +}; } // namespace d3d12 } // namespace gpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-viewer", - xe::gpu::d3d12::trace_viewer_main, "some.trace", - "target_trace_file"); +XE_DEFINE_WINDOWED_APP(xenia_gpu_d3d12_trace_viewer, + xe::gpu::d3d12::D3D12TraceViewer::Create); diff --git a/src/xenia/gpu/d3d12/premake5.lua b/src/xenia/gpu/d3d12/premake5.lua index fd1baf654..bc6e95653 100644 --- a/src/xenia/gpu/d3d12/premake5.lua +++ b/src/xenia/gpu/d3d12/premake5.lua @@ -54,7 +54,7 @@ project("xenia-gpu-d3d12-trace-viewer") }) files({ "d3d12_trace_viewer_main.cc", - "../../base/main_"..platform_suffix..".cc", + "../../ui/windowed_app_main_"..platform_suffix..".cc", }) -- Only create the .user file if it doesn't already exist. local user_file = project_root.."/build/xenia-gpu-d3d12-trace-viewer.vcxproj.user" @@ -101,7 +101,7 @@ project("xenia-gpu-d3d12-trace-dump") }) files({ "d3d12_trace_dump_main.cc", - "../../base/main_"..platform_suffix..".cc", + "../../base/console_app_main_"..platform_suffix..".cc", }) -- Only create the .user file if it doesn't already exist. local user_file = project_root.."/build/xenia-gpu-d3d12-trace-dump.vcxproj.user" diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 4113212fb..6ffab9150 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -18,7 +18,8 @@ #include "xenia/gpu/command_processor.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/ui/graphics_provider.h" -#include "xenia/ui/loop.h" +#include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" DEFINE_bool( store_shaders, true, @@ -57,22 +58,24 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, // This must happen on the UI thread. std::unique_ptr processor_context = nullptr; if (provider_) { - if (target_window_) { - target_window_->loop()->PostSynchronous([&]() { - // Create the context used for presentation. - assert_null(target_window->context()); - target_window_->set_context(provider_->CreateContext(target_window_)); - - // Setup the context the command processor will do all its drawing in. - // It's shared with the display context so that we can resolve - // framebuffers from it. - processor_context = provider()->CreateOffscreenContext(); - }); + // Setup the context the command processor will do all its drawing in. + bool contexts_initialized = true; + processor_context = provider()->CreateOffscreenContext(); + if (processor_context) { + if (target_window_) { + if (!target_window_->app_context().CallInUIThreadSynchronous([&]() { + // Create the context used for presentation. + assert_null(target_window->context()); + target_window_->set_context( + provider_->CreateContext(target_window_)); + })) { + contexts_initialized = false; + } + } } else { - processor_context = provider()->CreateOffscreenContext(); + contexts_initialized = false; } - - if (!processor_context) { + if (!contexts_initialized) { xe::FatalError( "Unable to initialize graphics context. Xenia requires Vulkan " "support.\n" diff --git a/src/xenia/gpu/null/null_graphics_system.cc b/src/xenia/gpu/null/null_graphics_system.cc index f1a88010f..c6df3ff09 100644 --- a/src/xenia/gpu/null/null_graphics_system.cc +++ b/src/xenia/gpu/null/null_graphics_system.cc @@ -26,7 +26,7 @@ X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor, ui::Window* target_window) { // This is a null graphics system, but we still setup vulkan because UI needs // it through us :| - provider_ = xe::ui::vulkan::VulkanProvider::Create(target_window); + provider_ = xe::ui::vulkan::VulkanProvider::Create(); return GraphicsSystem::Setup(processor, kernel_state, target_window); } diff --git a/src/xenia/gpu/premake5.lua b/src/xenia/gpu/premake5.lua index 87ccfc494..27e817f44 100644 --- a/src/xenia/gpu/premake5.lua +++ b/src/xenia/gpu/premake5.lua @@ -45,7 +45,7 @@ project("xenia-gpu-shader-compiler") }) files({ "shader_compiler_main.cc", - "../base/main_"..platform_suffix..".cc", + "../base/console_app_main_"..platform_suffix..".cc", }) filter("platforms:Windows") diff --git a/src/xenia/gpu/shader_compiler_main.cc b/src/xenia/gpu/shader_compiler_main.cc index 2ab319533..a3f9a7110 100644 --- a/src/xenia/gpu/shader_compiler_main.cc +++ b/src/xenia/gpu/shader_compiler_main.cc @@ -13,9 +13,9 @@ #include #include "xenia/base/assert.h" +#include "xenia/base/console_app_main.h" #include "xenia/base/cvar.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/platform.h" #include "xenia/base/string.h" #include "xenia/base/string_buffer.h" @@ -229,5 +229,6 @@ int shader_compiler_main(const std::vector& args) { } // namespace gpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-gpu-shader-compiler", xe::gpu::shader_compiler_main, - "shader.bin", "shader_input"); +XE_DEFINE_CONSOLE_APP("xenia-gpu-shader-compiler", + xe::gpu::shader_compiler_main, "shader.bin", + "shader_input"); diff --git a/src/xenia/gpu/trace_dump.cc b/src/xenia/gpu/trace_dump.cc index fdebcfba4..a78964d14 100644 --- a/src/xenia/gpu/trace_dump.cc +++ b/src/xenia/gpu/trace_dump.cc @@ -100,7 +100,7 @@ bool TraceDump::Setup() { return false; } graphics_system_ = emulator_->graphics_system(); - player_ = std::make_unique(nullptr, graphics_system_); + player_ = std::make_unique(graphics_system_); return true; } diff --git a/src/xenia/gpu/trace_player.cc b/src/xenia/gpu/trace_player.cc index 6152bafc5..8f2c98b8a 100644 --- a/src/xenia/gpu/trace_player.cc +++ b/src/xenia/gpu/trace_player.cc @@ -17,9 +17,8 @@ namespace xe { namespace gpu { -TracePlayer::TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system) - : loop_(loop), - graphics_system_(graphics_system), +TracePlayer::TracePlayer(GraphicsSystem* graphics_system) + : graphics_system_(graphics_system), current_frame_index_(0), current_command_index_(-1) { // Need to allocate all of physical memory so that we can write to it during diff --git a/src/xenia/gpu/trace_player.h b/src/xenia/gpu/trace_player.h index 897faaff7..d56205d59 100644 --- a/src/xenia/gpu/trace_player.h +++ b/src/xenia/gpu/trace_player.h @@ -16,7 +16,6 @@ #include "xenia/base/threading.h" #include "xenia/gpu/trace_protocol.h" #include "xenia/gpu/trace_reader.h" -#include "xenia/ui/loop.h" namespace xe { namespace gpu { @@ -30,7 +29,7 @@ enum class TracePlaybackMode { class TracePlayer : public TraceReader { public: - TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system); + TracePlayer(GraphicsSystem* graphics_system); ~TracePlayer() override; GraphicsSystem* graphics_system() const { return graphics_system_; } @@ -54,7 +53,6 @@ class TracePlayer : public TraceReader { void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size, TracePlaybackMode playback_mode, bool clear_caches); - xe::ui::Loop* loop_; GraphicsSystem* graphics_system_; int current_frame_index_; int current_command_index_; diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc index cfc82bf2a..d5f389276 100644 --- a/src/xenia/gpu/trace_viewer.cc +++ b/src/xenia/gpu/trace_viewer.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -13,10 +13,12 @@ #include "third_party/half/include/half.hpp" #include "third_party/imgui/imgui.h" +#include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/string.h" +#include "xenia/base/system.h" #include "xenia/base/threading.h" #include "xenia/gpu/command_processor.h" #include "xenia/gpu/gpu_flags.h" @@ -30,6 +32,7 @@ #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/virtual_key.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" DEFINE_path(target_trace_file, "", "Specifies the trace file to load.", "GPU"); @@ -46,22 +49,16 @@ static const ImVec4 kColorComment = static const ImVec4 kColorIgnored = ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f); -TraceViewer::TraceViewer() = default; +TraceViewer::TraceViewer(xe::ui::WindowedAppContext& app_context, + const std::string_view name) + : xe::ui::WindowedApp(app_context, name, "some.trace") { + AddPositionalOption("target_trace_file"); +} TraceViewer::~TraceViewer() = default; -int TraceViewer::Main(const std::vector& args) { - // Grab path from the flag or unnamed argument. - std::filesystem::path path; - if (!cvars::target_trace_file.empty()) { - // Passed as a named argument. - // TODO(benvanik): find something better than gflags that supports - // unicode. - path = cvars::target_trace_file; - } else if (args.size() >= 2) { - // Passed as an unnamed argument. - path = xe::to_path(args[1]); - } +bool TraceViewer::OnInitialize() { + std::filesystem::path path = cvars::target_trace_file; // If no path passed, ask the user. if (path.empty()) { @@ -83,42 +80,37 @@ int TraceViewer::Main(const std::vector& args) { } if (path.empty()) { - xe::FatalError("No trace file specified"); - return 1; + xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Warning, + "No trace file specified"); + return false; } // Normalize the path and make absolute. auto abs_path = std::filesystem::absolute(path); if (!Setup()) { - xe::FatalError("Unable to setup trace viewer"); - return 1; + xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error, + "Unable to setup trace viewer"); + return false; } if (!Load(std::move(abs_path))) { - xe::FatalError("Unable to load trace file; not found?"); - return 1; + xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error, + "Unable to load trace file; not found?"); + return false; } - Run(); - return 0; + return true; } bool TraceViewer::Setup() { // Main display window. - loop_ = ui::Loop::Create(); - window_ = xe::ui::Window::Create(loop_.get(), "xenia-gpu-trace-viewer"); - loop_->PostSynchronous([&]() { - xe::threading::set_name("Win32 Loop"); - if (!window_->Initialize()) { - xe::FatalError("Failed to initialize main window"); - return; - } - }); - window_->on_closed.AddListener([&](xe::ui::UIEvent* e) { - loop_->Quit(); - XELOGI("User-initiated death!"); - exit(1); - }); - loop_->on_quit.AddListener([&](xe::ui::UIEvent* e) { window_.reset(); }); + assert_true(app_context().IsInUIThread()); + window_ = xe::ui::Window::Create(app_context(), "xenia-gpu-trace-viewer"); + if (!window_->Initialize()) { + XELOGE("Failed to initialize main window"); + return false; + } + window_->on_closed.AddListener( + [this](xe::ui::UIEvent* e) { app_context().QuitFromUIThread(); }); window_->Resize(1920, 1200); // Create the emulator but don't initialize so we can setup the window. @@ -142,9 +134,9 @@ bool TraceViewer::Setup() { } }); - player_ = std::make_unique(loop_.get(), graphics_system_); + player_ = std::make_unique(graphics_system_); - window_->on_painting.AddListener([&](xe::ui::UIEvent* e) { + window_->on_painting.AddListener([this](xe::ui::UIEvent* e) { DrawUI(); // Continuous paint. @@ -167,16 +159,6 @@ bool TraceViewer::Load(const std::filesystem::path& trace_file_path) { return true; } -void TraceViewer::Run() { - // Wait until we are exited. - loop_->AwaitQuit(); - - player_.reset(); - emulator_.reset(); - window_.reset(); - loop_.reset(); -} - void TraceViewer::DrawMultilineString(const std::string_view str) { size_t i = 0; bool done = false; diff --git a/src/xenia/gpu/trace_viewer.h b/src/xenia/gpu/trace_viewer.h index e7477c284..1e22439fb 100644 --- a/src/xenia/gpu/trace_viewer.h +++ b/src/xenia/gpu/trace_viewer.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -18,13 +18,8 @@ #include "xenia/gpu/trace_protocol.h" #include "xenia/gpu/xenos.h" #include "xenia/memory.h" - -namespace xe { -namespace ui { -class Loop; -class Window; -} // namespace ui -} // namespace xe +#include "xenia/ui/window.h" +#include "xenia/ui/windowed_app.h" namespace xe { namespace gpu { @@ -32,14 +27,15 @@ namespace gpu { struct SamplerInfo; struct TextureInfo; -class TraceViewer { +class TraceViewer : public xe::ui::WindowedApp { public: virtual ~TraceViewer(); - int Main(const std::vector& args); + bool OnInitialize() override; protected: - TraceViewer(); + explicit TraceViewer(xe::ui::WindowedAppContext& app_context, + const std::string_view name); virtual std::unique_ptr CreateGraphicsSystem() = 0; @@ -60,7 +56,6 @@ class TraceViewer { virtual bool Setup(); - std::unique_ptr loop_; std::unique_ptr window_; std::unique_ptr emulator_; Memory* memory_ = nullptr; @@ -75,7 +70,6 @@ class TraceViewer { }; bool Load(const std::filesystem::path& trace_file_path); - void Run(); void DrawUI(); void DrawControllerUI(); diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index 7db3b21f8..a8afadd5b 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -62,7 +62,7 @@ project("xenia-gpu-vulkan-trace-viewer") }) files({ "vulkan_trace_viewer_main.cc", - "../../base/main_"..platform_suffix..".cc", + "../../ui/windowed_app_main_"..platform_suffix..".cc", }) filter("platforms:Linux") @@ -128,7 +128,7 @@ project("xenia-gpu-vulkan-trace-dump") }) files({ "vulkan_trace_dump_main.cc", - "../../base/main_"..platform_suffix..".cc", + "../../base/console_app_main_"..platform_suffix..".cc", }) filter("platforms:Linux") diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index f789e531d..9c852c706 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -37,7 +37,7 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor, kernel::KernelState* kernel_state, ui::Window* target_window) { // Must create the provider so we can create contexts. - auto provider = xe::ui::vulkan::VulkanProvider::Create(target_window); + auto provider = xe::ui::vulkan::VulkanProvider::Create(); device_ = provider->device(); provider_ = std::move(provider); diff --git a/src/xenia/gpu/vulkan/vulkan_trace_dump_main.cc b/src/xenia/gpu/vulkan/vulkan_trace_dump_main.cc index 16c1f34b8..d1d78047c 100644 --- a/src/xenia/gpu/vulkan/vulkan_trace_dump_main.cc +++ b/src/xenia/gpu/vulkan/vulkan_trace_dump_main.cc @@ -7,8 +7,8 @@ ****************************************************************************** */ +#include "xenia/base/console_app_main.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/gpu/trace_dump.h" #include "xenia/gpu/vulkan/vulkan_command_processor.h" #include "xenia/gpu/vulkan/vulkan_graphics_system.h" @@ -55,6 +55,6 @@ int trace_dump_main(const std::vector& args) { } // namespace gpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-gpu-vulkan-trace-dump", - xe::gpu::vulkan::trace_dump_main, "some.trace", - "target_trace_file"); +XE_DEFINE_CONSOLE_APP("xenia-gpu-vulkan-trace-dump", + xe::gpu::vulkan::trace_dump_main, "some.trace", + "target_trace_file"); diff --git a/src/xenia/gpu/vulkan/vulkan_trace_viewer_main.cc b/src/xenia/gpu/vulkan/vulkan_trace_viewer_main.cc index 769a1b8c8..ed296d4e0 100644 --- a/src/xenia/gpu/vulkan/vulkan_trace_viewer_main.cc +++ b/src/xenia/gpu/vulkan/vulkan_trace_viewer_main.cc @@ -2,13 +2,15 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ +#include +#include + #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/gpu/trace_viewer.h" #include "xenia/gpu/vulkan/vulkan_command_processor.h" #include "xenia/gpu/vulkan/vulkan_graphics_system.h" @@ -19,8 +21,13 @@ namespace vulkan { using namespace xe::gpu::xenos; -class VulkanTraceViewer : public TraceViewer { +class VulkanTraceViewer final : public TraceViewer { public: + static std::unique_ptr Create( + xe::ui::WindowedAppContext& app_context) { + return std::unique_ptr(new VulkanTraceViewer(app_context)); + } + std::unique_ptr CreateGraphicsSystem() override { return std::unique_ptr(new VulkanGraphicsSystem()); } @@ -60,17 +67,15 @@ class VulkanTraceViewer : public TraceViewer { // return static_cast(texture->handle); return 0; } -}; -int trace_viewer_main(const std::vector& args) { - VulkanTraceViewer trace_viewer; - return trace_viewer.Main(args); -} + private: + explicit VulkanTraceViewer(xe::ui::WindowedAppContext& app_context) + : TraceViewer(app_context, "xenia-gpu-vulkan-trace-viewer") {} +}; } // namespace vulkan } // namespace gpu } // namespace xe -DEFINE_ENTRY_POINT("xenia-gpu-vulkan-trace-viewer", - xe::gpu::vulkan::trace_viewer_main, "some.trace", - "target_trace_file"); +XE_DEFINE_WINDOWED_APP(xenia_gpu_vulkan_trace_viewer, + xe::gpu::vulkan::VulkanTraceViewer::Create); diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc index c1a81f533..d02735a42 100644 --- a/src/xenia/hid/hid_demo.cc +++ b/src/xenia/hid/hid_demo.cc @@ -10,15 +10,17 @@ #include #include #include +#include +#include #include #include +#include #include "third_party/fmt/include/fmt/format.h" #include "third_party/imgui/imgui.h" #include "xenia/base/clock.h" #include "xenia/base/cvar.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/threading.h" #include "xenia/hid/hid_flags.h" #include "xenia/hid/input_system.h" @@ -26,6 +28,7 @@ #include "xenia/ui/virtual_key.h" #include "xenia/ui/vulkan/vulkan_provider.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app.h" // Available input drivers: #include "xenia/hid/nop/nop_hid.h" @@ -46,10 +49,36 @@ DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]", namespace xe { namespace hid { -std::unique_ptr input_system_; -bool is_active = true; +class HidDemoApp final : public ui::WindowedApp { + public: + static std::unique_ptr Create( + ui::WindowedAppContext& app_context) { + return std::unique_ptr(new HidDemoApp(app_context)); + } -std::vector> CreateInputDrivers( + bool OnInitialize() override; + + private: + explicit HidDemoApp(ui::WindowedAppContext& app_context) + : ui::WindowedApp(app_context, "xenia-hid-demo") {} + + static std::vector> CreateInputDrivers( + ui::Window* window); + + void DrawUserInputGetState(uint32_t user_index) const; + void DrawInputGetState() const; + void DrawUserInputGetKeystroke(uint32_t user_index, bool poll, + bool hide_repeats, bool clear_log) const; + void DrawInputGetKeystroke(bool poll, bool hide_repeats, + bool clear_log) const; + + std::unique_ptr graphics_provider_; + std::unique_ptr window_; + std::unique_ptr input_system_; + bool is_active_ = true; +}; + +std::vector> HidDemoApp::CreateInputDrivers( ui::Window* window) { std::vector> drivers; if (cvars::hid.compare("nop") == 0) { @@ -94,60 +123,46 @@ std::vector> CreateInputDrivers( return drivers; } -std::unique_ptr CreateDemoGraphicsProvider( - xe::ui::Window* window) { - return xe::ui::vulkan::VulkanProvider::Create(window); -} +bool HidDemoApp::OnInitialize() { + // Create graphics provider that provides the context for the window. + graphics_provider_ = xe::ui::vulkan::VulkanProvider::Create(); + if (!graphics_provider_) { + return false; + } -void DrawInputGetState(); -void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log); - -int hid_demo_main(const std::vector& args) { - // Create run loop and the window. - auto loop = ui::Loop::Create(); - auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name); - loop->PostSynchronous([&window]() { - xe::threading::set_name("Win32 Loop"); - if (!window->Initialize()) { - FatalError("Failed to initialize main window"); - return; - } - }); - window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) { - loop->Quit(); + // Create the window. + window_ = xe::ui::Window::Create(app_context(), GetName()); + if (!window_->Initialize()) { + XELOGE("Failed to initialize main window"); + return false; + } + window_->on_closed.AddListener([this](xe::ui::UIEvent* e) { XELOGI("User-initiated death!"); - exit(1); + app_context().QuitFromUIThread(); }); - loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); }); // Initial size setting, done here so that it knows the menu exists. - window->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500); + window_->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500); - // Create the graphics context used for drawing and setup the window. - std::unique_ptr graphics_provider; - loop->PostSynchronous([&window, &graphics_provider]() { - // Create context and give it to the window. - // The window will finish initialization wtih the context (loading - // resources, etc). - graphics_provider = CreateDemoGraphicsProvider(window.get()); - window->set_context(graphics_provider->CreateContext(window.get())); + // Create the graphics context for the window. The window will finish + // initialization with the context (loading resources, etc). + window_->set_context(graphics_provider_->CreateContext(window_.get())); - // Initialize input system and all drivers. - input_system_ = std::make_unique(window.get()); - auto drivers = CreateInputDrivers(window.get()); - for (size_t i = 0; i < drivers.size(); ++i) { - auto& driver = drivers[i]; - driver->set_is_active_callback([]() -> bool { return is_active; }); - input_system_->AddDriver(std::move(driver)); - } + // Initialize input system and all drivers. + input_system_ = std::make_unique(window_.get()); + auto drivers = CreateInputDrivers(window_.get()); + for (size_t i = 0; i < drivers.size(); ++i) { + auto& driver = drivers[i]; + driver->set_is_active_callback([this]() -> bool { return is_active_; }); + input_system_->AddDriver(std::move(driver)); + } - window->Invalidate(); - }); + window_->Invalidate(); - window->set_imgui_input_enabled(true); + window_->set_imgui_input_enabled(true); - window->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = window->imgui_drawer()->GetIO(); + window_->on_painting.AddListener([this](xe::ui::UIEvent* e) { + auto& io = window_->imgui_drawer()->GetIO(); const ImGuiWindowFlags wflags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | @@ -161,7 +176,7 @@ int hid_demo_main(const std::vector& args) { ImVec2(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL)); ImGui::Text("Input System (hid) = \"%s\"", cvars::hid.c_str()); - ImGui::Checkbox("is_active", &is_active); + ImGui::Checkbox("is_active", &is_active_); } ImGui::End(); @@ -201,21 +216,13 @@ int hid_demo_main(const std::vector& args) { ImGui::End(); // Continuous paint. - window->Invalidate(); + window_->Invalidate(); }); - // Wait until we are exited. - loop->AwaitQuit(); - - input_system_.reset(); - - loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); }); - window.reset(); - loop.reset(); - return 0; + return true; } -void DrawUserInputGetState(uint32_t user_index) { +void HidDemoApp::DrawUserInputGetState(uint32_t user_index) const { ImGui::Text("User %u:", user_index); X_INPUT_STATE state; @@ -274,7 +281,7 @@ void DrawUserInputGetState(uint32_t user_index) { ImGui::Text(" "); } -void DrawInputGetState() { +void HidDemoApp::DrawInputGetState() const { ImGui::BeginChild("##input_get_state_scroll"); for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) { DrawUserInputGetState(user_index); @@ -282,46 +289,48 @@ void DrawInputGetState() { ImGui::EndChild(); } -static const std::unordered_map kVkPretty = { - {ui::VirtualKey::kXInputPadA, "A"}, - {ui::VirtualKey::kXInputPadB, "B"}, - {ui::VirtualKey::kXInputPadX, "X"}, - {ui::VirtualKey::kXInputPadY, "Y"}, - {ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"}, - {ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"}, - {ui::VirtualKey::kXInputPadLTrigger, "L Trigger"}, - {ui::VirtualKey::kXInputPadRTrigger, "R Trigger"}, +void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll, + bool hide_repeats, + bool clear_log) const { + static const std::unordered_map kVkPretty = + { + {ui::VirtualKey::kXInputPadA, "A"}, + {ui::VirtualKey::kXInputPadB, "B"}, + {ui::VirtualKey::kXInputPadX, "X"}, + {ui::VirtualKey::kXInputPadY, "Y"}, + {ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"}, + {ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"}, + {ui::VirtualKey::kXInputPadLTrigger, "L Trigger"}, + {ui::VirtualKey::kXInputPadRTrigger, "R Trigger"}, - {ui::VirtualKey::kXInputPadDpadUp, "DPad up"}, - {ui::VirtualKey::kXInputPadDpadDown, "DPad down"}, - {ui::VirtualKey::kXInputPadDpadLeft, "DPad left"}, - {ui::VirtualKey::kXInputPadDpadRight, "DPad right"}, - {ui::VirtualKey::kXInputPadStart, "Start"}, - {ui::VirtualKey::kXInputPadBack, "Back"}, - {ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"}, - {ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"}, + {ui::VirtualKey::kXInputPadDpadUp, "DPad up"}, + {ui::VirtualKey::kXInputPadDpadDown, "DPad down"}, + {ui::VirtualKey::kXInputPadDpadLeft, "DPad left"}, + {ui::VirtualKey::kXInputPadDpadRight, "DPad right"}, + {ui::VirtualKey::kXInputPadStart, "Start"}, + {ui::VirtualKey::kXInputPadBack, "Back"}, + {ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"}, + {ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"}, - {ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"}, - {ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"}, - {ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"}, - {ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"}, - {ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"}, - {ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"}, - {ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"}, - {ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"}, + {ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"}, + {ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"}, + {ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"}, + {ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"}, + {ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"}, + {ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"}, + {ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"}, + {ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"}, - {ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"}, - {ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"}, - {ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"}, - {ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"}, - {ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"}, - {ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"}, - {ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"}, - {ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"}, -}; + {ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"}, + {ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"}, + {ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"}, + {ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"}, + {ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"}, + {ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"}, + {ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"}, + {ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"}, + }; -void DrawUserInputGetKeystroke(uint32_t user_index, bool poll, - bool hide_repeats, bool clear_log) { const size_t maxLog = 128; static std::array, MAX_USERS> event_logs; static std::array last_event_times = {}; @@ -400,7 +409,8 @@ void DrawUserInputGetKeystroke(uint32_t user_index, bool poll, } } -void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) { +void HidDemoApp::DrawInputGetKeystroke(bool poll, bool hide_repeats, + bool clear_log) const { bool tab_bar = ImGui::BeginTabBar("DrawInputGetKeystroke"); for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) { DrawUserInputGetKeystroke(user_index, poll, hide_repeats, clear_log); @@ -411,4 +421,4 @@ void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) { } // namespace hid } // namespace xe -DEFINE_ENTRY_POINT("xenia-hid-demo", xe::hid::hid_demo_main, ""); +XE_DEFINE_WINDOWED_APP(xenia_hid_demo, xe::hid::HidDemoApp::Create); diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index ce89331c9..1aeef5657 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -32,7 +32,7 @@ project("xenia-hid-demo") }) files({ "hid_demo.cc", - "../base/main_"..platform_suffix..".cc", + "../ui/windowed_app_main_"..platform_suffix..".cc", }) resincludedirs({ project_root, diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index 0e4e7fd11..6b348f285 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -22,6 +22,7 @@ #include "xenia/hid/hid_flags.h" #include "xenia/ui/virtual_key.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" // TODO(joellinn) make this path relative to the config folder. DEFINE_path(mappings_file, "gamecontrollerdb.txt", @@ -43,6 +44,12 @@ SDLInputDriver::SDLInputDriver(xe::ui::Window* window) keystroke_states_() {} SDLInputDriver::~SDLInputDriver() { + // Make sure the CallInUIThread is executed before destroying the references. + if (sdl_pumpevents_queued_) { + window()->app_context().CallInUIThreadSynchronous([this]() { + window()->app_context().ExecutePendingFunctionsFromUIThread(); + }); + } for (size_t i = 0; i < controllers_.size(); i++) { if (controllers_.at(i).sdl) { SDL_GameControllerClose(controllers_.at(i).sdl); @@ -65,8 +72,9 @@ X_STATUS SDLInputDriver::Setup() { } // SDL_PumpEvents should only be run in the thread that initialized SDL - we - // are hijacking the window loop thread for that. - window()->loop()->PostSynchronous([&]() { + // are hijacking the UI thread for that. If this function fails to be queued, + // the "initialized" variables will be false - that's handled safely. + window()->app_context().CallInUIThreadSynchronous([this]() { if (!xe::helper::sdl::SDLHelper::Prepare()) { return; } @@ -129,7 +137,9 @@ X_STATUS SDLInputDriver::Setup() { } } }); - return sdl_events_initialized_ && sdl_gamecontroller_initialized_; + return (sdl_events_initialized_ && sdl_gamecontroller_initialized_) + ? X_STATUS_SUCCESS + : X_STATUS_UNSUCCESSFUL; } X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, @@ -693,7 +703,7 @@ void SDLInputDriver::QueueControllerUpdate() { bool is_queued = false; sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true); if (!is_queued) { - window()->loop()->Post([this]() { + window()->app_context().CallInUIThread([this]() { SDL_PumpEvents(); sdl_pumpevents_queued_ = false; }); diff --git a/src/xenia/kernel/xam/xam_nui.cc b/src/xenia/kernel/xam/xam_nui.cc index 2fc1a2a52..d0f99087d 100644 --- a/src/xenia/kernel/xam/xam_nui.cc +++ b/src/xenia/kernel/xam/xam_nui.cc @@ -15,6 +15,7 @@ #include "xenia/kernel/xam/xam_private.h" #include "xenia/ui/imgui_dialog.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" namespace xe { @@ -50,15 +51,16 @@ dword_result_t XamShowNuiTroubleshooterUI(unknown_t unk1, unknown_t unk2, auto display_window = kernel_state()->emulator()->display_window(); xe::threading::Fence fence; - display_window->loop()->PostSynchronous([&]() { - xe::ui::ImGuiDialog::ShowMessageBox( - display_window, "NUI Troubleshooter", - "The game has indicated there is a problem with NUI (Kinect).") - ->Then(&fence); - }); - ++xam_dialogs_shown_; - fence.Wait(); - --xam_dialogs_shown_; + if (display_window->app_context().CallInUIThreadSynchronous([&]() { + xe::ui::ImGuiDialog::ShowMessageBox( + display_window, "NUI Troubleshooter", + "The game has indicated there is a problem with NUI (Kinect).") + ->Then(&fence); + })) { + ++xam_dialogs_shown_; + fence.Wait(); + --xam_dialogs_shown_; + } return 0; } diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index b88ffbdb5..21b0590d6 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -17,6 +17,7 @@ #include "xenia/kernel/xam/xam_private.h" #include "xenia/ui/imgui_dialog.h" #include "xenia/ui/window.h" +#include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" namespace xe { @@ -76,11 +77,16 @@ X_RESULT xeXamDispatchDialog(T* dialog, result = close_callback(dialog); }); xe::threading::Fence fence; - kernel_state()->emulator()->display_window()->loop()->PostSynchronous( - [&dialog, &fence]() { dialog->Then(&fence); }); - ++xam_dialogs_shown_; - fence.Wait(); - --xam_dialogs_shown_; + xe::ui::WindowedAppContext& app_context = + kernel_state()->emulator()->display_window()->app_context(); + if (app_context.CallInUIThreadSynchronous( + [&dialog, &fence]() { dialog->Then(&fence); })) { + ++xam_dialogs_shown_; + fence.Wait(); + --xam_dialogs_shown_; + } else { + delete dialog; + } // dialog should be deleted at this point! return result; }; @@ -117,11 +123,14 @@ X_RESULT xeXamDispatchDialogEx( result = close_callback(dialog, extended_error, length); }); xe::threading::Fence fence; - display_window->loop()->PostSynchronous( - [&dialog, &fence]() { dialog->Then(&fence); }); - ++xam_dialogs_shown_; - fence.Wait(); - --xam_dialogs_shown_; + if (display_window->app_context().CallInUIThreadSynchronous( + [&dialog, &fence]() { dialog->Then(&fence); })) { + ++xam_dialogs_shown_; + fence.Wait(); + --xam_dialogs_shown_; + } else { + delete dialog; + } // dialog should be deleted at this point! return result; }; diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index e11a94296..43303c04e 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -11,10 +11,6 @@ #include -#ifdef XE_PLATFORM_WIN32 -#include -#endif - #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/byte_stream.h" #include "xenia/base/clock.h" @@ -383,20 +379,6 @@ X_STATUS XThread::Create() { // Set name immediately, if we have one. thread_->set_name(thread_name_); -#ifdef XE_PLATFORM_WIN32 - // Setup COM on this thread. - // - // https://devblogs.microsoft.com/oldnewthing/?p=4613 - // - // "If any thread in the process calls CoInitialize[Ex] with the - // COINIT_MULTITHREADED flag, then that not only initializes the current - // thread as a member of the multi-threaded apartment, but it also says, - // "Any thread which has never called CoInitialize[Ex] is also part of the - // multi-threaded apartment." -#pragma warning(suppress : 6031) - CoInitializeEx(nullptr, COINIT_MULTITHREADED); -#endif - // Profiler needs to know about the thread. xe::Profiler::ThreadEnter(thread_name_.c_str()); diff --git a/src/xenia/tools/api-scanner/api_scanner_loader.h b/src/xenia/tools/api-scanner/api_scanner_loader.h index 9aa8d7fe9..d4438cd19 100644 --- a/src/xenia/tools/api-scanner/api_scanner_loader.h +++ b/src/xenia/tools/api-scanner/api_scanner_loader.h @@ -7,11 +7,12 @@ ****************************************************************************** */ +#include +#include +#include #include -#include "xenia/base/main.h" #include "xenia/base/math.h" -#include "xenia/base/string.h" #include "xenia/cpu/export_resolver.h" #include "xenia/kernel/fs/filesystem.h" #include "xenia/kernel/objects/xfile.h" diff --git a/src/xenia/tools/api-scanner/api_scanner_main.cc b/src/xenia/tools/api-scanner/api_scanner_main.cc index 738e9622d..bd9e1fae3 100644 --- a/src/xenia/tools/api-scanner/api_scanner_main.cc +++ b/src/xenia/tools/api-scanner/api_scanner_main.cc @@ -7,17 +7,25 @@ ****************************************************************************** */ -#include "api_scanner_loader.h" +#include +#include +#include + +#include "xenia/base/console_app_main.h" +#include "xenia/base/cvar.h" +#include "xenia/base/string.h" +#include "xenia/tools/api_scanner_loader.h" namespace xe { namespace tools { -DEFINE_string(target, "", "List of file to extract imports from"); +DEFINE_transient_string(target, "", "List of file to extract imports from"); +// TODO(Triang3l): Change to std::string (currently doesn't even compile). int api_scanner_main(std::vector& args) { // XXX we need gflags to split multiple flags into arrays for us - if (args.size() == 2 || !FLAGS_target.empty()) { + if (args.size() == 2 || !cvars::target.empty()) { apiscanner_loader loader_; std::wstring target(cvars::target.empty() ? args[1] : xe::to_wstring(cvars::target)); @@ -41,5 +49,5 @@ int api_scanner_main(std::vector& args) { } // namespace tools } // namespace xe -DEFINE_ENTRY_POINT(L"api-scanner", L"api-scanner --target=", - xe::tools::api_scanner_main); +XE_DEFINE_CONSOLE_APP("api-scanner", xe::tools::api_scanner_main, + "[target file]", "target"); diff --git a/src/xenia/ui/d3d12/d3d12_provider.cc b/src/xenia/ui/d3d12/d3d12_provider.cc index 9560cfa97..57345824d 100644 --- a/src/xenia/ui/d3d12/d3d12_provider.cc +++ b/src/xenia/ui/d3d12/d3d12_provider.cc @@ -47,8 +47,8 @@ bool D3D12Provider::IsD3D12APIAvailable() { return true; } -std::unique_ptr D3D12Provider::Create(Window* main_window) { - std::unique_ptr provider(new D3D12Provider(main_window)); +std::unique_ptr D3D12Provider::Create() { + std::unique_ptr provider(new D3D12Provider); if (!provider->Initialize()) { xe::FatalError( "Unable to initialize Direct3D 12 graphics subsystem.\n" @@ -63,9 +63,6 @@ std::unique_ptr D3D12Provider::Create(Window* main_window) { return provider; } -D3D12Provider::D3D12Provider(Window* main_window) - : GraphicsProvider(main_window) {} - D3D12Provider::~D3D12Provider() { if (graphics_analysis_ != nullptr) { graphics_analysis_->Release(); diff --git a/src/xenia/ui/d3d12/d3d12_provider.h b/src/xenia/ui/d3d12/d3d12_provider.h index 25c159dcd..3ddaf507d 100644 --- a/src/xenia/ui/d3d12/d3d12_provider.h +++ b/src/xenia/ui/d3d12/d3d12_provider.h @@ -27,7 +27,7 @@ class D3D12Provider : public GraphicsProvider { static bool IsD3D12APIAvailable(); - static std::unique_ptr Create(Window* main_window); + static std::unique_ptr Create(); std::unique_ptr CreateContext( Window* target_window) override; @@ -147,7 +147,7 @@ class D3D12Provider : public GraphicsProvider { } private: - explicit D3D12Provider(Window* main_window); + D3D12Provider() = default; static bool EnableIncreaseBasePriorityPrivilege(); bool Initialize(); diff --git a/src/xenia/ui/d3d12/d3d12_window_demo.cc b/src/xenia/ui/d3d12/d3d12_window_demo.cc index d42e6cb0e..93b65ac67 100644 --- a/src/xenia/ui/d3d12/d3d12_window_demo.cc +++ b/src/xenia/ui/d3d12/d3d12_window_demo.cc @@ -2,28 +2,44 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ +#include #include -#include -#include "xenia/base/main.h" #include "xenia/ui/d3d12/d3d12_provider.h" -#include "xenia/ui/window.h" +#include "xenia/ui/window_demo.h" +#include "xenia/ui/windowed_app.h" namespace xe { namespace ui { +namespace d3d12 { -int window_demo_main(const std::vector& args); +class D3D12WindowDemoApp final : public WindowDemoApp { + public: + static std::unique_ptr Create(WindowedAppContext& app_context) { + return std::unique_ptr(new D3D12WindowDemoApp(app_context)); + } -std::unique_ptr CreateDemoGraphicsProvider(Window* window) { - return xe::ui::d3d12::D3D12Provider::Create(window); + protected: + std::unique_ptr CreateGraphicsProvider() const override; + + private: + explicit D3D12WindowDemoApp(WindowedAppContext& app_context) + : WindowDemoApp(app_context, "xenia-ui-window-d3d12-demo") {} +}; + +std::unique_ptr D3D12WindowDemoApp::CreateGraphicsProvider() + const { + return D3D12Provider::Create(); } +} // namespace d3d12 } // namespace ui } // namespace xe -DEFINE_ENTRY_POINT("xenia-ui-window-d3d12-demo", xe::ui::window_demo_main, ""); +XE_DEFINE_WINDOWED_APP(xenia_ui_window_d3d12_demo, + xe::ui::d3d12::D3D12WindowDemoApp::Create); diff --git a/src/xenia/ui/d3d12/premake5.lua b/src/xenia/ui/d3d12/premake5.lua index 1615ee611..0e33011e5 100644 --- a/src/xenia/ui/d3d12/premake5.lua +++ b/src/xenia/ui/d3d12/premake5.lua @@ -30,7 +30,7 @@ project("xenia-ui-window-d3d12-demo") files({ "../window_demo.cc", "d3d12_window_demo.cc", - project_root.."/src/xenia/base/main_"..platform_suffix..".cc", + project_root.."/src/xenia/ui/windowed_app_main_"..platform_suffix..".cc", }) resincludedirs({ project_root, diff --git a/src/xenia/ui/file_picker_win.cc b/src/xenia/ui/file_picker_win.cc index 7af0fd57e..d2d7bf33d 100644 --- a/src/xenia/ui/file_picker_win.cc +++ b/src/xenia/ui/file_picker_win.cc @@ -12,6 +12,9 @@ #include "xenia/base/string.h" #include "xenia/ui/file_picker.h" +// Microsoft headers after platform_win.h. +#include + namespace xe { namespace ui { @@ -106,32 +109,16 @@ Win32FilePicker::Win32FilePicker() = default; Win32FilePicker::~Win32FilePicker() = default; -template -struct com_ptr { - com_ptr() : value(nullptr) {} - ~com_ptr() { reset(); } - void reset() { - if (value) { - value->Release(); - value = nullptr; - } - } - operator T*() { return value; } - T* operator->() const { return value; } - T** addressof() { return &value; } - T* value; -}; - bool Win32FilePicker::Show(void* parent_window_handle) { // TODO(benvanik): FileSaveDialog. assert_true(mode() == Mode::kOpen); // TODO(benvanik): folder dialogs. assert_true(type() == Type::kFile); - com_ptr file_dialog; + Microsoft::WRL::ComPtr file_dialog; HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(file_dialog.addressof())); + IID_PPV_ARGS(&file_dialog)); if (!SUCCEEDED(hr)) { return false; } @@ -180,14 +167,13 @@ bool Win32FilePicker::Show(void* parent_window_handle) { } // Create an event handling object, and hook it up to the dialog. - com_ptr file_dialog_events; - hr = CDialogEventHandler_CreateInstance( - IID_PPV_ARGS(file_dialog_events.addressof())); + Microsoft::WRL::ComPtr file_dialog_events; + hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&file_dialog_events)); if (!SUCCEEDED(hr)) { return false; } DWORD cookie; - hr = file_dialog->Advise(file_dialog_events, &cookie); + hr = file_dialog->Advise(file_dialog_events.Get(), &cookie); if (!SUCCEEDED(hr)) { return false; } @@ -201,8 +187,8 @@ bool Win32FilePicker::Show(void* parent_window_handle) { // Obtain the result once the user clicks the 'Open' button. // The result is an IShellItem object. - com_ptr shell_item; - hr = file_dialog->GetResult(shell_item.addressof()); + Microsoft::WRL::ComPtr shell_item; + hr = file_dialog->GetResult(&shell_item); if (!SUCCEEDED(hr)) { return false; } diff --git a/src/xenia/ui/graphics_provider.h b/src/xenia/ui/graphics_provider.h index ccb9ab771..8bbfe90d0 100644 --- a/src/xenia/ui/graphics_provider.h +++ b/src/xenia/ui/graphics_provider.h @@ -36,9 +36,6 @@ class GraphicsProvider { virtual ~GraphicsProvider() = default; - // The 'main' window of an application, used to query provider information. - Window* main_window() const { return main_window_; } - // Creates a new graphics context and swapchain for presenting to a window. virtual std::unique_ptr CreateContext( Window* target_window) = 0; @@ -48,9 +45,7 @@ class GraphicsProvider { virtual std::unique_ptr CreateOffscreenContext() = 0; protected: - explicit GraphicsProvider(Window* main_window) : main_window_(main_window) {} - - Window* main_window_ = nullptr; + GraphicsProvider() = default; }; } // namespace ui diff --git a/src/xenia/ui/loop.cc b/src/xenia/ui/loop.cc deleted file mode 100644 index 4fc6ad1dc..000000000 --- a/src/xenia/ui/loop.cc +++ /dev/null @@ -1,37 +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/loop.h" - -#include "xenia/base/assert.h" -#include "xenia/base/threading.h" - -namespace xe { -namespace ui { - -Loop::Loop() = default; - -Loop::~Loop() = default; - -void Loop::PostSynchronous(std::function fn) { - if (is_on_loop_thread()) { - // Prevent deadlock if we are executing on ourselves. - fn(); - return; - } - xe::threading::Fence fence; - Post([&fn, &fence]() { - fn(); - fence.Signal(); - }); - fence.Wait(); -} - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/loop.h b/src/xenia/ui/loop.h deleted file mode 100644 index 06e49e461..000000000 --- a/src/xenia/ui/loop.h +++ /dev/null @@ -1,45 +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_LOOP_H_ -#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: - static std::unique_ptr Create(); - - Loop(); - virtual ~Loop(); - - // Returns true if the currently executing code is within the loop thread. - virtual bool is_on_loop_thread() = 0; - - 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 -} // namespace xe - -#endif // XENIA_UI_LOOP_H_ diff --git a/src/xenia/ui/loop_gtk.cc b/src/xenia/ui/loop_gtk.cc deleted file mode 100644 index a2b704aeb..000000000 --- a/src/xenia/ui/loop_gtk.cc +++ /dev/null @@ -1,81 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/loop_gtk.h" - -#include -#include - -#include "xenia/base/assert.h" - -namespace xe { -namespace ui { - -std::unique_ptr Loop::Create() { return std::make_unique(); } - -GTKLoop::GTKLoop() : thread_id_() { - gtk_init(nullptr, nullptr); - xe::threading::Fence init_fence; - thread_ = std::thread([&init_fence, this]() { - xe::threading::set_name("GTK Loop"); - - thread_id_ = std::this_thread::get_id(); - init_fence.Signal(); - - ThreadMain(); - - quit_fence_.Signal(); - }); - init_fence.Wait(); -} - -GTKLoop::~GTKLoop() { - Quit(); - thread_.join(); -} - -void GTKLoop::ThreadMain() { gtk_main(); } - -bool GTKLoop::is_on_loop_thread() { - return thread_id_ == std::this_thread::get_id(); -} - -gboolean _posted_fn_thunk(gpointer posted_fn) { - // convert gpointer back to std::function, call it, then free std::function - std::function* f = - reinterpret_cast*>(posted_fn); - std::function& func = *f; - func(); - delete f; - // Tells GDK we don't want to run this again - return G_SOURCE_REMOVE; -} - -void GTKLoop::Post(std::function fn) { - assert_true(thread_id_ != std::thread::id()); - // converting std::function to a generic pointer for gdk - gdk_threads_add_idle(_posted_fn_thunk, reinterpret_cast( - new std::function(fn))); -} - -void GTKLoop::PostDelayed(std::function fn, uint64_t delay_millis) { - gdk_threads_add_timeout( - delay_millis, _posted_fn_thunk, - reinterpret_cast(new std::function(fn))); -} - -void GTKLoop::Quit() { - assert_true(thread_id_ != std::thread::id()); - Post([]() { gtk_main_quit(); }); -} - -void GTKLoop::AwaitQuit() { quit_fence_.Wait(); } - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/loop_win.cc b/src/xenia/ui/loop_win.cc deleted file mode 100644 index 2cd57f5fc..000000000 --- a/src/xenia/ui/loop_win.cc +++ /dev/null @@ -1,131 +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/loop_win.h" - -#include "xenia/base/assert.h" - -namespace xe { -namespace ui { - -std::unique_ptr Loop::Create() { return std::make_unique(); } - -Win32Loop::Win32Loop() : thread_id_(0) { - timer_queue_ = CreateTimerQueue(); - - xe::threading::Fence init_fence; - thread_ = std::thread([&init_fence, this]() { - xe::threading::set_name("Win32 Loop"); - thread_id_ = GetCurrentThreadId(); - - // Make a Win32 call to enable the thread queue. - MSG msg; - PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); - - init_fence.Signal(); - - ThreadMain(); - - quit_fence_.Signal(); - }); - init_fence.Wait(); -} - -Win32Loop::~Win32Loop() { - Quit(); - thread_.join(); - - DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE); - std::lock_guard lock(pending_timers_mutex_); - while (!pending_timers_.empty()) { - auto timer = pending_timers_.back(); - pending_timers_.pop_back(); - delete timer; - } -} - -void Win32Loop::ThreadMain() { - MSG msg; - while (!should_exit_ && GetMessage(&msg, NULL, 0, 0)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - - // Process queued functions. - std::lock_guard lock(posted_functions_mutex_); - for (auto it = posted_functions_.begin(); it != posted_functions_.end();) { - (*it).Call(); - it = posted_functions_.erase(it); - } - } - - UIEvent e(nullptr); - on_quit(&e); -} - -bool Win32Loop::is_on_loop_thread() { - return thread_id_ == GetCurrentThreadId(); -} - -void Win32Loop::Post(std::function fn) { - assert_true(thread_id_ != 0); - { - std::lock_guard lock(posted_functions_mutex_); - PostedFn posted_fn(fn); - posted_functions_.push_back(posted_fn); - } - - while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) { - Sleep(1); - } -} - -void Win32Loop::TimerQueueCallback(void* context, uint8_t) { - auto timer = reinterpret_cast(context); - auto loop = timer->loop; - auto fn = std::move(timer->fn); - DeleteTimerQueueTimer(timer->timer_queue, timer->timer_handle, NULL); - { - std::lock_guard lock(loop->pending_timers_mutex_); - loop->pending_timers_.remove(timer); - } - delete timer; - loop->Post(std::move(fn)); -} - -void Win32Loop::PostDelayed(std::function fn, uint64_t delay_millis) { - if (!delay_millis) { - Post(std::move(fn)); - return; - } - auto timer = new PendingTimer(); - timer->loop = this; - timer->timer_queue = timer_queue_; - timer->fn = std::move(fn); - { - std::lock_guard lock(pending_timers_mutex_); - pending_timers_.push_back(timer); - } - CreateTimerQueueTimer(&timer->timer_handle, timer_queue_, - WAITORTIMERCALLBACK(TimerQueueCallback), timer, - DWORD(delay_millis), 0, - WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE); -} - -void Win32Loop::Quit() { - assert_true(thread_id_ != 0); - should_exit_ = true; - while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) { - Sleep(1); - } -} - -void Win32Loop::AwaitQuit() { quit_fence_.Wait(); } - -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/loop_win.h b/src/xenia/ui/loop_win.h deleted file mode 100644 index bd2d28ae2..000000000 --- a/src/xenia/ui/loop_win.h +++ /dev/null @@ -1,74 +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_LOOP_WIN_H_ -#define XENIA_UI_LOOP_WIN_H_ - -#include -#include -#include - -#include "xenia/base/platform_win.h" -#include "xenia/base/threading.h" -#include "xenia/ui/loop.h" - -namespace xe { -namespace ui { - -class Win32Loop : public Loop { - public: - Win32Loop(); - ~Win32Loop() override; - - bool is_on_loop_thread() override; - - void Post(std::function fn) override; - void PostDelayed(std::function fn, uint64_t delay_millis) override; - - void Quit() override; - void AwaitQuit() override; - - private: - struct PendingTimer { - Win32Loop* loop; - HANDLE timer_queue; - HANDLE timer_handle; - std::function fn; - }; - - class PostedFn { - public: - explicit PostedFn(std::function fn) : fn_(std::move(fn)) {} - void Call() { fn_(); } - - private: - std::function fn_; - }; - - void ThreadMain(); - - static void TimerQueueCallback(void* context, uint8_t); - - std::thread thread_; - DWORD thread_id_; - bool should_exit_ = false; - xe::threading::Fence quit_fence_; - - HANDLE timer_queue_; - std::mutex pending_timers_mutex_; - std::list pending_timers_; - - std::recursive_mutex posted_functions_mutex_; - std::list posted_functions_; -}; - -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_LOOP_WIN_H_ diff --git a/src/xenia/ui/premake5.lua b/src/xenia/ui/premake5.lua index c5dbd84af..93c012600 100644 --- a/src/xenia/ui/premake5.lua +++ b/src/xenia/ui/premake5.lua @@ -13,3 +13,4 @@ project("xenia-ui") }) local_platform_files() removefiles({"*_demo.cc"}) + removefiles({"windowed_app_main_*.cc"}) diff --git a/src/xenia/ui/vulkan/premake5.lua b/src/xenia/ui/vulkan/premake5.lua index 3f675ad0a..56365f278 100644 --- a/src/xenia/ui/vulkan/premake5.lua +++ b/src/xenia/ui/vulkan/premake5.lua @@ -45,7 +45,7 @@ project("xenia-ui-window-vulkan-demo") files({ "../window_demo.cc", "vulkan_window_demo.cc", - project_root.."/src/xenia/base/main_"..platform_suffix..".cc", + project_root.."/src/xenia/ui/windowed_app_main_"..platform_suffix..".cc", }) resincludedirs({ project_root, diff --git a/src/xenia/ui/vulkan/vulkan_provider.cc b/src/xenia/ui/vulkan/vulkan_provider.cc index f9e9eea13..d67a5e263 100644 --- a/src/xenia/ui/vulkan/vulkan_provider.cc +++ b/src/xenia/ui/vulkan/vulkan_provider.cc @@ -24,8 +24,8 @@ namespace xe { namespace ui { namespace vulkan { -std::unique_ptr VulkanProvider::Create(Window* main_window) { - std::unique_ptr provider(new VulkanProvider(main_window)); +std::unique_ptr VulkanProvider::Create() { + std::unique_ptr provider(new VulkanProvider); if (!provider->Initialize()) { xe::FatalError( "Unable to initialize Vulkan graphics subsystem.\n" @@ -40,9 +40,6 @@ std::unique_ptr VulkanProvider::Create(Window* main_window) { return provider; } -VulkanProvider::VulkanProvider(Window* main_window) - : GraphicsProvider(main_window) {} - VulkanProvider::~VulkanProvider() { device_.reset(); instance_.reset(); diff --git a/src/xenia/ui/vulkan/vulkan_provider.h b/src/xenia/ui/vulkan/vulkan_provider.h index f4a8080e3..12c303785 100644 --- a/src/xenia/ui/vulkan/vulkan_provider.h +++ b/src/xenia/ui/vulkan/vulkan_provider.h @@ -25,7 +25,7 @@ class VulkanProvider : public GraphicsProvider { public: ~VulkanProvider() override; - static std::unique_ptr Create(Window* main_window); + static std::unique_ptr Create(); VulkanInstance* instance() const { return instance_.get(); } VulkanDevice* device() const { return device_.get(); } @@ -35,7 +35,7 @@ class VulkanProvider : public GraphicsProvider { std::unique_ptr CreateOffscreenContext() override; protected: - explicit VulkanProvider(Window* main_window); + VulkanProvider() = default; bool Initialize(); diff --git a/src/xenia/ui/vulkan/vulkan_window_demo.cc b/src/xenia/ui/vulkan/vulkan_window_demo.cc index 12965197b..f2e0fcff8 100644 --- a/src/xenia/ui/vulkan/vulkan_window_demo.cc +++ b/src/xenia/ui/vulkan/vulkan_window_demo.cc @@ -2,28 +2,44 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ +#include #include -#include -#include "xenia/base/main.h" #include "xenia/ui/vulkan/vulkan_provider.h" -#include "xenia/ui/window.h" +#include "xenia/ui/window_demo.h" +#include "xenia/ui/windowed_app.h" namespace xe { namespace ui { +namespace vulkan { -int window_demo_main(const std::vector& args); +class VulkanWindowDemoApp final : public WindowDemoApp { + public: + static std::unique_ptr Create(WindowedAppContext& app_context) { + return std::unique_ptr(new VulkanWindowDemoApp(app_context)); + } -std::unique_ptr CreateDemoGraphicsProvider(Window* window) { - return xe::ui::vulkan::VulkanProvider::Create(window); + protected: + std::unique_ptr CreateGraphicsProvider() const override; + + private: + explicit VulkanWindowDemoApp(WindowedAppContext& app_context) + : WindowDemoApp(app_context, "xenia-ui-window-vulkan-demo") {} +}; + +std::unique_ptr VulkanWindowDemoApp::CreateGraphicsProvider() + const { + return VulkanProvider::Create(); } +} // namespace vulkan } // namespace ui } // namespace xe -DEFINE_ENTRY_POINT("xenia-ui-window-vulkan-demo", xe::ui::window_demo_main, ""); +XE_DEFINE_WINDOWED_APP(xenia_ui_window_vulkan_demo, + xe::ui::vulkan::VulkanWindowDemoApp::Create); diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 47a2b874c..74b81ae21 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -31,8 +31,8 @@ constexpr double kDoubleClickDistance = 5; constexpr int32_t kMouseWheelDetent = 120; -Window::Window(Loop* loop, const std::string& title) - : loop_(loop), title_(title) {} +Window::Window(WindowedAppContext& app_context, const std::string& title) + : app_context_(app_context), title_(title) {} Window::~Window() { // Context must have been cleaned up already in OnDestroy. diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index 56094553b..2627c7ca0 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -16,10 +16,10 @@ #include "xenia/base/delegate.h" #include "xenia/base/platform.h" #include "xenia/ui/graphics_context.h" -#include "xenia/ui/loop.h" #include "xenia/ui/menu_item.h" #include "xenia/ui/ui_event.h" #include "xenia/ui/window_listener.h" +#include "xenia/ui/windowed_app_context.h" namespace xe { namespace ui { @@ -31,11 +31,13 @@ class ImGuiDrawer; class Window { public: - static std::unique_ptr Create(Loop* loop, const std::string& title); + static std::unique_ptr Create(WindowedAppContext& app_context_, + const std::string& title); virtual ~Window(); - Loop* loop() const { return loop_; } + WindowedAppContext& app_context() const { return app_context_; } + virtual NativePlatformHandle native_platform_handle() const = 0; virtual NativeWindowHandle native_handle() const = 0; @@ -69,8 +71,11 @@ class Window { virtual bool is_bordered() const { return false; } virtual void set_bordered(bool enabled) {} - virtual int get_dpi() const { return 96; } - virtual float get_dpi_scale() const { return get_dpi() / 96.f; } + virtual int get_medium_dpi() const { return 96; } + virtual int get_dpi() const { return get_medium_dpi(); } + virtual float get_dpi_scale() const { + return float(get_dpi()) / float(get_medium_dpi()); + } bool has_focus() const { return has_focus_; } virtual void set_focus(bool value) { has_focus_ = value; } @@ -78,6 +83,8 @@ class Window { bool is_cursor_visible() const { return is_cursor_visible_; } virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; } + // TODO(Triang3l): Don't scale for guest output - use physical pixels. Use + // logical pixels only for the immediate drawer. int32_t width() const { return width_; } int32_t height() const { return height_; } int32_t scaled_width() const { return int32_t(width_ * get_dpi_scale()); } @@ -135,7 +142,7 @@ class Window { Delegate on_mouse_wheel; protected: - Window(Loop* loop, const std::string& title); + Window(WindowedAppContext& app_context, const std::string& title); void ForEachListener(std::function fn); void TryForEachListener(std::function fn); @@ -170,7 +177,7 @@ class Window { void OnKeyPress(KeyEvent* e, bool is_down, bool is_char); - Loop* loop_ = nullptr; + WindowedAppContext& app_context_; std::unique_ptr main_menu_; std::string title_; #ifdef XE_PLATFORM_GNU_LINUX diff --git a/src/xenia/ui/window_demo.cc b/src/xenia/ui/window_demo.cc index 4f94a1fc3..05d61611d 100644 --- a/src/xenia/ui/window_demo.cc +++ b/src/xenia/ui/window_demo.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -12,7 +12,6 @@ #include "third_party/imgui/imgui.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/profiling.h" #include "xenia/base/threading.h" #include "xenia/ui/graphics_provider.h" @@ -20,28 +19,29 @@ #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/virtual_key.h" #include "xenia/ui/window.h" +#include "xenia/ui/window_demo.h" namespace xe { namespace ui { -// Implemented in one of the window_*_demo.cc files under a subdir. -std::unique_ptr CreateDemoGraphicsProvider(Window* window); +WindowDemoApp::~WindowDemoApp() { Profiler::Shutdown(); } -int window_demo_main(const std::vector& args) { +bool WindowDemoApp::OnInitialize() { Profiler::Initialize(); - Profiler::ThreadEnter("main"); + Profiler::ThreadEnter("Main"); - // Create run loop and the window. - auto loop = ui::Loop::Create(); - auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name); - loop->PostSynchronous([&window]() { - xe::threading::set_name("Win32 Loop"); - xe::Profiler::ThreadEnter("Win32 Loop"); - if (!window->Initialize()) { - FatalError("Failed to initialize main window"); - return; - } - }); + // Create graphics provider that provides the context for the window. + graphics_provider_ = CreateGraphicsProvider(); + if (!graphics_provider_) { + return false; + } + + // Create the window. + window_ = xe::ui::Window::Create(app_context(), GetName()); + if (!window_->Initialize()) { + XELOGE("Failed to initialize main window"); + return false; + } // Main menu. auto main_menu = MenuItem::Create(MenuItem::Type::kNormal); @@ -49,7 +49,7 @@ int window_demo_main(const std::vector& args) { { file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close", "Alt+F4", - [&window]() { window->Close(); })); + [this]() { window_->Close(); })); } main_menu->AddChild(std::move(file_menu)); auto debug_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Debug"); @@ -62,37 +62,28 @@ int window_demo_main(const std::vector& args) { []() { Profiler::TogglePause(); })); } main_menu->AddChild(std::move(debug_menu)); - window->set_main_menu(std::move(main_menu)); + window_->set_main_menu(std::move(main_menu)); // Initial size setting, done here so that it knows the menu exists. - window->Resize(1920, 1200); + window_->Resize(1920, 1200); - // Create the graphics context used for drawing and setup the window. - std::unique_ptr graphics_provider; - loop->PostSynchronous([&window, &graphics_provider]() { - // Create graphics provider and an initial context for the window. - // The window will finish initialization with the context (loading - // resources, etc). - graphics_provider = CreateDemoGraphicsProvider(window.get()); - window->set_context(graphics_provider->CreateContext(window.get())); + // Create the graphics context for the window. The window will finish + // initialization with the context (loading resources, etc). + window_->set_context(graphics_provider_->CreateContext(window_.get())); - // Setup the profiler display. - GraphicsContextLock context_lock(window->context()); - Profiler::set_window(window.get()); + // Setup the profiler display. + GraphicsContextLock context_lock(window_->context()); + Profiler::set_window(window_.get()); - // Enable imgui input. - window->set_imgui_input_enabled(true); + // Enable imgui input. + window_->set_imgui_input_enabled(true); + + window_->on_closed.AddListener([this](xe::ui::UIEvent* e) { + XELOGI("User-initiated death!"); + app_context().QuitFromUIThread(); }); - window->on_closed.AddListener( - [&loop, &window, &graphics_provider](xe::ui::UIEvent* e) { - loop->Quit(); - Profiler::Shutdown(); - XELOGI("User-initiated death!"); - }); - loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); }); - - window->on_key_down.AddListener([](xe::ui::KeyEvent* e) { + window_->on_key_down.AddListener([](xe::ui::KeyEvent* e) { switch (e->virtual_key()) { case VirtualKey::kF3: Profiler::ToggleDisplay(); @@ -102,23 +93,19 @@ int window_demo_main(const std::vector& args) { } }); - window->on_painting.AddListener([&](xe::ui::UIEvent* e) { - auto& io = window->imgui_drawer()->GetIO(); + window_->on_painting.AddListener([this](xe::ui::UIEvent* e) { + auto& io = window_->imgui_drawer()->GetIO(); ImGui::ShowDemoWindow(); ImGui::ShowMetricsWindow(); + Profiler::Flip(); + // Continuous paint. - window->Invalidate(); + window_->Invalidate(); }); - // Wait until we are exited. - loop->AwaitQuit(); - - window.reset(); - loop.reset(); - graphics_provider.reset(); - return 0; + return true; } } // namespace ui diff --git a/src/xenia/ui/window_demo.h b/src/xenia/ui/window_demo.h new file mode 100644 index 000000000..29b6bc1fa --- /dev/null +++ b/src/xenia/ui/window_demo.h @@ -0,0 +1,44 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOW_DEMO_H_ +#define XENIA_UI_WINDOW_DEMO_H_ + +#include +#include + +#include "xenia/ui/graphics_provider.h" +#include "xenia/ui/window.h" +#include "xenia/ui/windowed_app.h" + +namespace xe { +namespace ui { + +class WindowDemoApp : public WindowedApp { + public: + ~WindowDemoApp(); + + bool OnInitialize() override; + + protected: + explicit WindowDemoApp(WindowedAppContext& app_context, + const std::string_view name) + : WindowedApp(app_context, name) {} + + virtual std::unique_ptr CreateGraphicsProvider() const = 0; + + private: + std::unique_ptr graphics_provider_; + std::unique_ptr window_; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOW_DEMO_H_ diff --git a/src/xenia/ui/window_gtk.cc b/src/xenia/ui/window_gtk.cc index cf272619b..b35f91305 100644 --- a/src/xenia/ui/window_gtk.cc +++ b/src/xenia/ui/window_gtk.cc @@ -22,12 +22,13 @@ namespace xe { namespace ui { -std::unique_ptr Window::Create(Loop* loop, const std::string& title) { - return std::make_unique(loop, title); +std::unique_ptr Window::Create(WindowedAppContext& app_context, + const std::string& title) { + return std::make_unique(app_context, title); } -GTKWindow::GTKWindow(Loop* loop, const std::string& title) - : Window(loop, title) {} +GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string& title) + : Window(app_context, title) {} GTKWindow::~GTKWindow() { OnDestroy(); @@ -92,7 +93,7 @@ gboolean close_callback(GtkWidget* widget, gpointer data) { return G_SOURCE_CONTINUE; } -void GTKWindow::Create() { +bool GTKWindow::OnCreate() { // GTK optionally allows passing argv and argc here for parsing gtk specific // options. We won't bother window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); @@ -129,10 +130,7 @@ void GTKWindow::Create() { gtk_box_pack_end(GTK_BOX(box_), drawing_area_, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(window_), box_); gtk_widget_show_all(window_); -} -bool GTKWindow::OnCreate() { - loop()->PostSynchronous([this]() { this->Create(); }); return super::OnCreate(); } diff --git a/src/xenia/ui/window_gtk.h b/src/xenia/ui/window_gtk.h index 27a4c08b0..6677f2fc7 100644 --- a/src/xenia/ui/window_gtk.h +++ b/src/xenia/ui/window_gtk.h @@ -28,7 +28,7 @@ class GTKWindow : public Window { using super = Window; public: - GTKWindow(Loop* loop, const std::string& title); + GTKWindow(WindowedAppContext& app_context, const std::string& title); ~GTKWindow() override; NativePlatformHandle native_platform_handle() const override { @@ -74,7 +74,6 @@ class GTKWindow : public Window { void OnResize(UIEvent* e) override; private: - void Create(); GtkWidget* window_; GtkWidget* box_; GtkWidget* drawing_area_; diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index a88cad529..a6d2c68ae 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -18,16 +18,19 @@ #include "xenia/base/logging.h" #include "xenia/base/platform_win.h" #include "xenia/ui/virtual_key.h" +#include "xenia/ui/windowed_app_context_win.h" namespace xe { namespace ui { -std::unique_ptr Window::Create(Loop* loop, const std::string& title) { - return std::make_unique(loop, title); +std::unique_ptr Window::Create(WindowedAppContext& app_context, + const std::string& title) { + return std::make_unique(app_context, title); } -Win32Window::Win32Window(Loop* loop, const std::string& title) - : Window(loop, title) {} +Win32Window::Win32Window(WindowedAppContext& app_context, + const std::string& title) + : Window(app_context, title) {} Win32Window::~Win32Window() { OnDestroy(); @@ -43,37 +46,32 @@ Win32Window::~Win32Window() { } NativePlatformHandle Win32Window::native_platform_handle() const { - return ::GetModuleHandle(nullptr); + return static_cast(app_context()).hinstance(); } bool Win32Window::Initialize() { return OnCreate(); } bool Win32Window::OnCreate() { - HINSTANCE hInstance = GetModuleHandle(nullptr); + HINSTANCE hInstance = + static_cast(app_context()).hinstance(); - if (!SetProcessDpiAwareness_ || !GetDpiForMonitor_) { + // Per-monitor DPI awareness is expected to be enabled via the manifest, as + // that's the recommended way, which also doesn't require calling + // SetProcessDpiAwareness before doing anything that may depend on DPI + // awareness (so it's safe to use any Windows APIs before this code). + // TODO(Triang3l): Safe handling of per-monitor DPI awareness v2, with + // automatic scaling on DPI change. + if (!GetDpiForMonitor_) { auto shcore = GetModuleHandleW(L"shcore.dll"); if (shcore) { - SetProcessDpiAwareness_ = - GetProcAddress(shcore, "SetProcessDpiAwareness"); GetDpiForMonitor_ = GetProcAddress(shcore, "GetDpiForMonitor"); } } static bool has_registered_class = false; if (!has_registered_class) { - // Tell Windows that we're DPI aware. - if (SetProcessDpiAwareness_) { - auto spda = (decltype(&SetProcessDpiAwareness))SetProcessDpiAwareness_; - auto res = spda(PROCESS_PER_MONITOR_DPI_AWARE); - if (res != S_OK) { - XELOGW("Failed to set process DPI awareness. (code = 0x{:08X})", - static_cast(res)); - } - } - - WNDCLASSEX wcex; - wcex.cbSize = sizeof(WNDCLASSEX); + WNDCLASSEXW wcex; + wcex.cbSize = sizeof(wcex); wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wcex.lpfnWndProc = Win32Window::WndProcThunk; wcex.cbClsExtra = 0; @@ -85,7 +83,7 @@ bool Win32Window::OnCreate() { wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = L"XeniaWindowClass"; - if (!RegisterClassEx(&wcex)) { + if (!RegisterClassExW(&wcex)) { XELOGE("RegisterClassEx failed"); return false; } @@ -216,7 +214,9 @@ bool Win32Window::SetIcon(const void* buffer, size_t size) { } // Reset icon to default. - auto default_icon = LoadIconW(GetModuleHandle(nullptr), L"MAINICON"); + auto default_icon = LoadIconW( + static_cast(app_context()).hinstance(), + L"MAINICON"); SendMessageW(hwnd_, WM_SETICON, ICON_BIG, reinterpret_cast(default_icon)); SendMessageW(hwnd_, WM_SETICON, ICON_SMALL, @@ -316,13 +316,22 @@ void Win32Window::set_bordered(bool enabled) { } int Win32Window::get_dpi() const { + // TODO(Triang3l): Cache until WM_DPICHANGED is received (which, with + // per-monitor awareness v2 will also receive the new suggested window size). + // According to MSDN, x and y are identical. + if (!GetDpiForMonitor_) { - return 96; + HDC screen_hdc = GetDC(nullptr); + if (!screen_hdc) { + return get_medium_dpi(); + } + int logical_pixels_x = GetDeviceCaps(screen_hdc, LOGPIXELSX); + ReleaseDC(nullptr, screen_hdc); + return logical_pixels_x; } HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY); - // According to msdn, x and y are identical... UINT dpi_x, dpi_y; auto gdfm = (decltype(&GetDpiForMonitor))GetDpiForMonitor_; gdfm(monitor, MDT_DEFAULT, &dpi_x, &dpi_y); diff --git a/src/xenia/ui/window_win.h b/src/xenia/ui/window_win.h index fe16d943f..f4dceaeac 100644 --- a/src/xenia/ui/window_win.h +++ b/src/xenia/ui/window_win.h @@ -24,7 +24,7 @@ class Win32Window : public Window { using super = Window; public: - Win32Window(Loop* loop, const std::string& title); + Win32Window(WindowedAppContext& app_context, const std::string& title); ~Win32Window() override; NativePlatformHandle native_platform_handle() const override; @@ -90,7 +90,6 @@ class Win32Window : public Window { WINDOWPLACEMENT windowed_pos_ = {0}; POINT last_mouse_pos_ = {0}; - void* SetProcessDpiAwareness_ = nullptr; void* GetDpiForMonitor_ = nullptr; }; diff --git a/src/xenia/ui/windowed_app.h b/src/xenia/ui/windowed_app.h new file mode 100644 index 000000000..14eba9cb7 --- /dev/null +++ b/src/xenia/ui/windowed_app.h @@ -0,0 +1,129 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOWED_APP_H_ +#define XENIA_UI_WINDOWED_APP_H_ + +#include +#include +#include +#include + +#include "xenia/base/platform.h" +#include "xenia/ui/windowed_app_context.h" + +#if XE_PLATFORM_ANDROID +#include + +#include "xenia/ui/windowed_app_context_android.h" +#endif + +namespace xe { +namespace ui { + +// Interface between the platform's entry points (in the main, UI, thread that +// also runs the message loop) and the app that implements it. +class WindowedApp { + public: + // WindowedApps are expected to provide a static creation function, for + // creating an instance of the class (which may be called before + // initialization of platform-specific parts, should preferably be as simple + // as possible). + + WindowedApp(const WindowedApp& app) = delete; + WindowedApp& operator=(const WindowedApp& app) = delete; + virtual ~WindowedApp() = default; + + WindowedAppContext& app_context() const { return app_context_; } + + // Same as the executable (project), xenia-library-app. + const std::string& GetName() const { return name_; } + const std::string& GetPositionalOptionsUsage() const { + return positional_options_usage_; + } + const std::vector& GetPositionalOptions() const { + return positional_options_; + } + + // Called once before receiving other lifecycle callback invocations. Cvars + // will be initialized with the launch arguments. Returns whether the app has + // been initialized successfully (otherwise platform-specific code must call + // OnDestroy and refuse to continue running the app). + virtual bool OnInitialize() = 0; + // See OnDestroy for more info. + void InvokeOnDestroy() { + // For safety and convenience of referencing objects owned by the app in + // pending functions queued in or after OnInitialize, make sure they are + // executed before telling the app that destruction needs to happen. + app_context().ExecutePendingFunctionsFromUIThread(); + OnDestroy(); + } + + protected: + // Positional options should be initialized in the constructor if needed. + // Cvars will not have been initialized with the arguments at the moment of + // construction (as the result depends on construction). + explicit WindowedApp( + WindowedAppContext& app_context, const std::string_view name, + const std::string_view positional_options_usage = std::string_view()) + : app_context_(app_context), + name_(name), + positional_options_usage_(positional_options_usage) {} + + // For calling from the constructor. + void AddPositionalOption(const std::string_view option) { + positional_options_.emplace_back(option); + } + + // OnDestroy entry point may be called (through InvokeOnDestroy) by the + // platform-specific lifecycle interface at request of either the app itself + // or the OS - thus should be possible for the lifecycle interface to call at + // any moment (not from inside other lifecycle callbacks though). The app will + // also be destroyed when that happens, so the destructor will also be called + // (but this is more safe with respect to exceptions). This is only guaranteed + // to be called if OnInitialize has already happened (successfully or not) - + // in case of an error before initialization, the destructor may be called + // alone as well. Context's pending functions will be executed before the + // call, so it's safe to destroy dependencies of them here (though it may + // still be possible to add more pending functions here depending on whether + // the context was explicitly shut down before this is invoked). + virtual void OnDestroy() {} + + private: + WindowedAppContext& app_context_; + + std::string name_; + std::string positional_options_usage_; + std::vector positional_options_; +}; + +#if XE_PLATFORM_ANDROID +// Multiple apps in a single library. ANativeActivity_onCreate chosen via +// android.app.func_name of the NativeActivity of each app. +#define XE_DEFINE_WINDOWED_APP(export_name, creator) \ + __attribute__((visibility("default"))) extern "C" void export_name( \ + ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \ + xe::ui::AndroidWindowedAppContext::StartAppOnNativeActivityCreate( \ + activity, saved_state, saved_state_size, creator); \ + } +#else +// Separate executables for each app. +std::unique_ptr (*GetWindowedAppCreator())( + WindowedAppContext& app_context); +#define XE_DEFINE_WINDOWED_APP(export_name, creator) \ + std::unique_ptr (*xe::ui::GetWindowedAppCreator())( \ + xe::ui::WindowedAppContext & app_context) { \ + return creator; \ + } +#endif + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOWED_APP_H_ diff --git a/src/xenia/ui/windowed_app_context.cc b/src/xenia/ui/windowed_app_context.cc new file mode 100644 index 000000000..ee7c3d619 --- /dev/null +++ b/src/xenia/ui/windowed_app_context.cc @@ -0,0 +1,180 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/windowed_app_context.h" + +#include + +#include "xenia/base/assert.h" +#include "xenia/base/threading.h" + +namespace xe { +namespace ui { + +WindowedAppContext::~WindowedAppContext() { + // The UI thread is responsible for managing the lifetime of the context. + assert_true(IsInUIThread()); + + // It's okay to destroy the context from a platform's internal UI loop + // callback, primarily on platforms where the loop is run by the OS itself, + // and the context can't be created and destroyed in a RAII way, rather, it's + // created in an initialization handler and destroyed in a shutdown handler + // called by the OS. However, destruction must not be done from within the + // queued functions - as in this case, the pending function container, the + // mutex, will be accessed after having been destroyed already. + + // Make sure CallInUIThreadDeferred doesn't call + // NotifyUILoopOfPendingFunctions, which is virtual. + is_in_destructor_ = true; + // Make sure the final ExecutePendingFunctionsFromUIThread doesn't call + // PlatformQuitFromUIThread, which is virtual. + has_quit_ = true; + + // Platform-specific quit is expected to be performed by the subclass (the + // order of it vs. the final ExecutePendingFunctionsFromUIThread shouldn't + // matter anymore, the implementation may assume that no pending functions + // will be requested for execution specifically via the platform-specific + // loop, as there should be no more references to the context in other + // threads), can't call the virtual PlatformQuitFromUIThread anymore. + ExecutePendingFunctionsFromUIThread(true); +} + +bool WindowedAppContext::CallInUIThreadDeferred( + std::function function) { + { + std::unique_lock pending_functions_lock( + pending_functions_mutex_); + if (!pending_functions_accepted_) { + // Will not be called as the loop will not be executed anymore. + return false; + } + pending_functions_.emplace_back(std::move(function)); + } + // Notify unconditionally, even if currently running pending functions. It's + // possible for pending functions themselves to run inner platform message + // loops, such as when displaying dialogs - in this case, the notification is + // needed to run the new function from such an inner loop. A modal loop can be + // started even in leftovers happening during the quit, where there's still + // opportunity for enqueueing and executing new pending functions - so only + // checking if called in the destructor (it's safe to check this without + // locking a mutex as it's assumed that if the object is already being + // destroyed, no other threads can have references to it - any access would + // result in a race condition anyway) as the subclass has already been + // destroyed. Having pending_functions_mutex_ unlocked also means that + // NotifyUILoopOfPendingFunctions may be done while the UI thread is calling + // or has already called PlatformQuitFromUIThread - but it's better than + // keeping pending_functions_mutex_ locked as NotifyUILoopOfPendingFunctions + // may be implemented as pushing to a fixed-size pipe, in which case it will + // have to wait until free space is available, but if the UI thread tries to + // lock the mutex afterwards to execute pending functions (and encouters + // contention), nothing will be able to receive from the pipe anymore and thus + // free the space, causing a deadlock. + if (!is_in_destructor_) { + NotifyUILoopOfPendingFunctions(); + } + return true; +} + +bool WindowedAppContext::CallInUIThread(std::function function) { + if (IsInUIThread()) { + // The intention is just to make sure the code is executed in the UI thread, + // don't defer execution if no need to. + function(); + return true; + } + return CallInUIThreadDeferred(std::move(function)); +} + +bool WindowedAppContext::CallInUIThreadSynchronous( + std::function function) { + if (IsInUIThread()) { + // Prevent deadlock if called from the UI thread. + function(); + return true; + } + xe::threading::Fence fence; + if (!CallInUIThreadDeferred([&function, &fence]() { + function(); + fence.Signal(); + })) { + return false; + } + fence.Wait(); + return true; +} + +void WindowedAppContext::QuitFromUIThread() { + assert_true(IsInUIThread()); + bool has_quit_previously = has_quit_; + // Make sure PlatformQuitFromUIThread is called only once, not from nested + // pending function execution during the quit - otherwise it will be called + // when it's still possible to add new pending functions. This isn't as wrong + // as calling PlatformQuitFromUIThread from the destructor, but still a part + // of the contract for simplicity. + has_quit_ = true; + // Executing pending function unconditionally because it's the contract of + // this method that functions are executed immediately. + ExecutePendingFunctionsFromUIThread(true); + if (has_quit_previously) { + // Potentially calling QuitFromUIThread from inside a pending function (in + // the worst and dangerous case, from a pending function executed in the + // destructor - and PlatformQuitFromUIThread is virtual). + return; + } + // Call the platform-specific shutdown while letting it assume that no new + // functions will be queued anymore (but NotifyUILoopOfPendingFunctions may + // still be called after PlatformQuitFromUIThread as the two are not + // interlocked). This is different than the order in the destruction, but + // there this assumption is ensured by the expectation that there should be no + // more references to the context in other threads that would allow queueing + // new functions with calling NotifyUILoopOfPendingFunctions. + PlatformQuitFromUIThread(); +} + +void WindowedAppContext::ExecutePendingFunctionsFromUIThread(bool is_final) { + assert_true(IsInUIThread()); + std::unique_lock pending_functions_lock(pending_functions_mutex_); + while (!pending_functions_.empty()) { + // Removing the function from the queue before executing it, as the function + // itself may call ExecutePendingFunctionsFromUIThread - if it's kept, the + // inner loop will try to execute it again, resulting in potentially endless + // recursion, and even if it's terminated, each level will be trying to + // remove the same function from the queue - instead, actually removing + // other functions, or even beyond the end of the queue. + std::function function = std::move(pending_functions_.front()); + pending_functions_.pop_front(); + // Call the function with the lock released as it may take an indefinitely + // long time to execute if it opens some dialog (possibly with its own + // platform message loop), and in that case, without unlocking, no other + // thread would be able to add new pending functions (which would result in + // unintended waits for user input). This also allows using std::mutex + // instead of std::recursive_mutex. + pending_functions_lock.unlock(); + function(); + pending_functions_lock.lock(); + } + if (is_final) { + // Atomically with completion of the pending functions loop, disallow adding + // new functions after executing the existing ones - it was possible to + // enqueue new functions from the leftover ones as there still was + // opportunity to call them, so it wasn't necessary to disallow adding + // before executing, but now new functions will potentially never be + // executed. This is done even if this is just an inner pending functions + // execution and there's still potential possibility of adding and executing + // new functions in the outer loops - for simplicity and consistency (so + // QuitFromUIThread's behavior doesn't depend as much on the location of the + // call - inside a pending function or from some system callback of the + // window), assuming after a PlatformQuitFromUIThread call, it's not + // possible to add new pending functions anymore. + pending_functions_accepted_ = false; + } +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/windowed_app_context.h b/src/xenia/ui/windowed_app_context.h new file mode 100644 index 000000000..eb377cc20 --- /dev/null +++ b/src/xenia/ui/windowed_app_context.h @@ -0,0 +1,184 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_H_ +#define XENIA_UI_WINDOWED_APP_CONTEXT_H_ + +#include +#include +#include +#include +#include + +#include "xenia/base/assert.h" + +namespace xe { +namespace ui { + +// Context for inputs provided by the entry point and interacting with the +// platform's UI loop, to be implemented by platforms. +class WindowedAppContext { + public: + WindowedAppContext(const WindowedAppContext& context) = delete; + WindowedAppContext& operator=(const WindowedAppContext& context) = delete; + virtual ~WindowedAppContext(); + + // The thread where the object is created will be assumed to be the UI thread, + // for the purpose of being able to perform CallInUIThreadSynchronous before + // running the loop. + bool IsInUIThread() const { + return std::this_thread::get_id() == ui_thread_id_; + } + + // CallInUIThreadDeferred and CallInUIThread are fire and forget - will be + // executed at some point the future when the UI thread is running the loop + // and is not busy doing other things. + // Therefore, references to objects in the function may outlive the owners of + // those objects, so use-after-free is very easy to create if not being + // careful enough. + // There are two solutions to this issue: + // - Signaling a fence in the function, awaiting it before destroying objects + // referenced by the function (works for shutdown from non-UI threads, or + // for CallInUIThread, but not CallInUIThreadDeferred, in the UI thread). + // - Calling ExecutePendingFunctionsFromUIThread in the UI thread before + // destroying objects referenced by the function (works for shutdown from + // the UI thread, though CallInUIThreadSynchronous doing + // ExecutePendingFunctionsFromUIThread is also an option for shutdown from + // any thread). + // (These are not required if all the called function is doing is triggering a + // quit with the context pointer captured by value, as the only object + // involved will be the context itself, with a pointer that is valid until + // it's destroyed - the most late location of the pending function execution + // possible.) + // Returning true if the function has been enqueued (it will be called at some + // point, at worst, before exiting the loop) or called immediately, false if + // it was dropped (if calling after exiting the loop). + // It's okay to enqueue functions from queued functions already being + // executed. As execution may be happening during the destruction of the + // context as well, in this case, CallInUIThreadDeferred must make sure it + // doesn't call anything virtual in the destructor. + + // Enqueues the function regardless of the current thread. Won't necessarily + // be executed directly from the platform main loop as + // ExecutePendingFunctionsFromUIThread may be called anywhere (for instance, + // to await pending functions before destroying what they are referencing). + bool CallInUIThreadDeferred(std::function function); + // Executes the function immediately if already in the UI thread, enqueues it + // otherwise. + bool CallInUIThread(std::function function); + bool CallInUIThreadSynchronous(std::function function); + // It's okay to call this function from the queued functions themselves (such + // as in the case of waiting for a pending async CallInUIThreadDeferred + // described above - the wait may be done inside a CallInUIThreadSynchronous + // function safely). + void ExecutePendingFunctionsFromUIThread() { + ExecutePendingFunctionsFromUIThread(false); + } + + // If on the target platform, the program itself is supposed to run the UI + // loop, this may be checked before doing blocking message waits as an + // additional safety measure beyond what PlatformQuitFromUIThread guarantees, + // and if true, the loop should be terminated (pending function will already + // have been executed). This doesn't imply that pending functions have been + // executed in all contexts, however - they can be executing from quitting + // itself (in the worst case, in the destructor where virtual methods can't be + // called), and in this case, this will be returning true. + bool HasQuitFromUIThread() const { + assert_true(IsInUIThread()); + return has_quit_; + } + + // Immediately disallows adding new pending UI functions, executes the already + // queued ones, and makes sure that the UI loop is aware that it was asked to + // stop running. This must not destroy the context or the app directly - the + // actual app shutdown will be initiated at some point outside the scope of + // app's callbacks. May call virtual functions - invoke + // ExecutePendingFunctionsFromUIThread(true) in the destructor instead, and + // use the platform-specific destructor to invoke the needed + // PlatformQuitFromUIThread logic (it should be safe, in case of destruction, + // to perform platform-specific quit request logic before the common part - + // NotifyUILoopOfPendingFunctions won't be called after the platform-specific + // quit request logic in this case anyway as destruction expects that there + // are no references to the object in other threads). Safe to call from within + // pending functions - requesting quit from non-UI threads is possible via + // methods like CallInUIThreadSynchronous. For deferred, rather than + // immediate, quitting from the UI thread, CallInUIThreadDeferred may be used + // (but the context pointer should be captured by value not to require + // explicit completion forcing in case the storage of the pointer is lost + // before the function is called). + void QuitFromUIThread(); + // Callable from any thread. This is a special case where a completely + // fire-and-forget CallInUIThreadDeferred is safe, as the function only + // references nonvirtual functions of the context itself, and will be called + // at most from the destructor. No need to return the result - it doesn't + // matter if has quit already or not, as that's the intention anyway. + void RequestDeferredQuit() { + CallInUIThreadDeferred([this] { QuitFromUIThread(); }); + } + + protected: + WindowedAppContext() : ui_thread_id_(std::this_thread::get_id()) {} + + // Can be called from any thread (including the UI thread) to ask the OS to + // run an iteration of the UI loop (with or without processing internal UI + // messages, this is platform-dependent) so pending functions will be executed + // at some point. pending_functions_mutex_ will not be locked when this is + // called (to make sure that, for example, the caller, waiting for space in + // the OS's message queue, such as in case of a Linux pipe, won't be blocking + // the UI thread that has started executing pending messages while pumping + // that pipe, resulting in a deadlock) - implementations don't directly see + // anything protected by it anyway, and a spurious notification shouldn't be + // causing any damage, this is similar to how condition variables can be + // signaled outside the critical section (signaling inside the critical + // section may also cause contention if the thread waiting is woken up quickly + // enough). This, however, means that NotifyUILoopOfPendingFunctions may be + // called in a non-UI thread after the final pending message processing + // followed by PlatformQuitFromUIThread in the UI thread - so it can still be + // called after a quit. + virtual void NotifyUILoopOfPendingFunctions() = 0; + + // Called when requesting a quit in the UI thread to tell the platform that + // the UI loop needs to be terminated. The pending function queue is assumed + // to be empty before this is called, and no new pending functions can be + // added (but NotifyUILoopOfPendingFunctions may still be called as it's not + // mutually exclusive with PlatformQuitFromUIThread - if this matters, the + // platform implementation itself should be resolving this case). + virtual void PlatformQuitFromUIThread() = 0; + + std::thread::id ui_thread_id_; + + private: + // May be called with is_final == true from the destructor - must not call + // anything virtual in this case. + void ExecutePendingFunctionsFromUIThread(bool is_final); + + // Accessible by the UI thread. + bool has_quit_ = false; + bool is_in_destructor_ = false; + + // Synchronizes producers with each other and with the consumer, as well as + // all of them with the pending_functions_accepted_ variable which indicates + // whether shutdown has not been performed yet, as after the shutdown, no new + // functions will be executed anymore, therefore no new function should be + // queued, as it will never be called (thus CallInUIThreadSynchronous for it + // will never return, for instance). + std::mutex pending_functions_mutex_; + std::deque> pending_functions_; + // Protected by pending_functions_mutex_, writable by the UI thread, readable + // by any thread. Must be set to false before exiting the main platform loop, + // but before that, all pending functions must be executed no matter what, as + // they may need to signal fences currently being awaited (like the + // CallInUIThreadSynchronous fence). + bool pending_functions_accepted_ = true; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOWED_APP_CONTEXT_H_ diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc new file mode 100644 index 000000000..e2dbe1aa5 --- /dev/null +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -0,0 +1,67 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/windowed_app_context_android.h" + +#include + +#include "xenia/base/assert.h" +#include "xenia/ui/windowed_app.h" + +namespace xe { +namespace ui { + +void AndroidWindowedAppContext::StartAppOnNativeActivityCreate( + ANativeActivity* activity, [[maybe_unused]] void* saved_state, + [[maybe_unused]] size_t saved_state_size, + std::unique_ptr (*app_creator)( + WindowedAppContext& app_context)) { + AndroidWindowedAppContext* app_context = + new AndroidWindowedAppContext(activity); + // The pointer is now held by the Activity as its ANativeActivity::instance, + // until the destruction. + if (!app_context->InitializeApp(app_creator)) { + delete app_context; + ANativeActivity_finish(activity); + } +} + +AndroidWindowedAppContext::~AndroidWindowedAppContext() { + // TODO(Triang3l): Unregister activity callbacks. + activity_->instance = nullptr; +} + +void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { + // TODO(Triang3l): Request message processing in the UI thread. +} + +void AndroidWindowedAppContext::PlatformQuitFromUIThread() { + ANativeActivity_finish(activity); +} + +AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity) + : activity_(activity) { + activity_->instance = this; + // TODO(Triang3l): Register activity callbacks. +} + +bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( + *app_creator)(WindowedAppContext& app_context)) { + assert_null(app_); + app_ = app_creator(this); + if (!app_->OnInitialize()) { + app_->InvokeOnDestroy(); + app_.reset(); + return false; + } + return true; +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h new file mode 100644 index 000000000..08ddc31bc --- /dev/null +++ b/src/xenia/ui/windowed_app_context_android.h @@ -0,0 +1,61 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ +#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ + +#include +#include + +#include "xenia/ui/windowed_app_context.h" + +namespace xe { +namespace ui { + +class WindowedApp; + +class AndroidWindowedAppContext final : public WindowedAppContext { + public: + // For calling from android.app.func_name exports. + static void StartAppOnNativeActivityCreate( + ANativeActivity* activity, void* saved_state, size_t saved_state_size, + std::unique_ptr (*app_creator)( + WindowedAppContext& app_context)); + + // Defined in the translation unit where WindowedApp is complete because of + // std::unique_ptr. + ~AndroidWindowedAppContext(); + + ANativeActivity* activity() const { return activity_; } + WindowedApp* app() const { return app_.get(); } + + void NotifyUILoopOfPendingFunctions() override; + + void PlatformQuitFromUIThread() override; + + private: + explicit AndroidWindowedAppContext(ANativeActivity* activity); + bool InitializeApp(std::unique_ptr (*app_creator)( + WindowedAppContext& app_context)); + + // TODO(Triang3l): Switch from ANativeActivity to the context itself being the + // object for communication with the Java code when NativeActivity isn't used + // anymore as its functionality is heavily limited. + ANativeActivity* activity_; + std::unique_ptr app_; + + // TODO(Triang3l): The rest of the context, including quit handler (and the + // destructor) calling `finish` on the activity, UI looper notification + // posting, etc. +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ diff --git a/src/xenia/ui/windowed_app_context_gtk.cc b/src/xenia/ui/windowed_app_context_gtk.cc new file mode 100644 index 000000000..ea092929c --- /dev/null +++ b/src/xenia/ui/windowed_app_context_gtk.cc @@ -0,0 +1,112 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/windowed_app_context_gtk.h" + +#include +#include +#include + +namespace xe { +namespace ui { + +GTKWindowedAppContext::~GTKWindowedAppContext() { + // Remove the idle sources as their data pointer (to this context) is now + // outdated. + if (quit_idle_pending_) { + g_source_remove(quit_idle_pending_); + } + { + // Lock the mutex for a pending_functions_idle_pending_ access memory + // barrier, even though no other threads can access this object anymore. + std::lock_guard pending_functions_idle_pending_lock( + pending_functions_idle_pending_mutex_); + if (pending_functions_idle_pending_) { + g_source_remove(pending_functions_idle_pending_); + } + } +} + +void GTKWindowedAppContext::NotifyUILoopOfPendingFunctions() { + std::lock_guard pending_functions_idle_pending_lock( + pending_functions_idle_pending_mutex_); + if (!pending_functions_idle_pending_) { + pending_functions_idle_pending_ = + gdk_threads_add_idle(PendingFunctionsSourceFunc, this); + } +} + +void GTKWindowedAppContext::PlatformQuitFromUIThread() { + if (quit_idle_pending_ || !gtk_main_level()) { + return; + } + gtk_main_quit(); + // Quit from all loops in the context, current inner, upcoming inner (this is + // why the idle function is added even at the main level of 1), until we can + // return to the tail of RunMainGTKLoop. + quit_idle_pending_ = gdk_threads_add_idle(QuitSourceFunc, this); +} + +void GTKWindowedAppContext::RunMainGTKLoop() { + // For safety, in case the quit request somehow happened before the loop. + if (HasQuitFromUIThread()) { + return; + } + assert_zero(gtk_main_level()); + gtk_main(); + // Something else - not QuitFromUIThread - might have done gtk_main_quit for + // the outer loop. If it has exited for some reason, let the context know, so + // pending function won't be added pointlessly. + QuitFromUIThread(); + // Don't interfere with main loops of other contexts that will possibly be + // executed in this thread later (if this is not the only app that will be + // executed in this process, for example, or if some WindowedAppContext-less + // dialog is opened after the windowed app has quit) - cancel the scheduled + // quit chain as we have quit already. + if (quit_idle_pending_) { + g_source_remove(quit_idle_pending_); + quit_idle_pending_ = 0; + } +} + +gboolean GTKWindowedAppContext::PendingFunctionsSourceFunc(gpointer data) { + auto& app_context = *static_cast(data); + // Allow new pending function notifications to be made, and stop tracking the + // lifetime of the idle source that is already being executed and thus + // removed. This must be done before executing the functions (if done after, + // notifications may be missed if they happen between the execution of the + // functions and the reset of pending_functions_idle_pending_). + { + std::lock_guard pending_functions_idle_pending_lock( + app_context.pending_functions_idle_pending_mutex_); + app_context.pending_functions_idle_pending_ = 0; + } + app_context.ExecutePendingFunctionsFromUIThread(); + return G_SOURCE_REMOVE; +} + +gboolean GTKWindowedAppContext::QuitSourceFunc(gpointer data) { + auto app_context = static_cast(data); + guint main_level = gtk_main_level(); + assert_not_zero(main_level); + if (!main_level) { + app_context->quit_idle_pending_ = 0; + return G_SOURCE_REMOVE; + } + gtk_main_quit(); + // Quit from all loops in the context, current inner, upcoming inner (this is + // why the idle function is added even at the main level of 1), until we can + // return to the tail of RunMainGTKLoop. + app_context->quit_idle_pending_ = + gdk_threads_add_idle(QuitSourceFunc, app_context); + return G_SOURCE_REMOVE; +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/windowed_app_context_gtk.h b/src/xenia/ui/windowed_app_context_gtk.h new file mode 100644 index 000000000..198e7a7a9 --- /dev/null +++ b/src/xenia/ui/windowed_app_context_gtk.h @@ -0,0 +1,46 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_ +#define XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_ + +#include +#include + +#include "xenia/ui/windowed_app_context.h" + +namespace xe { +namespace ui { + +class GTKWindowedAppContext final : public WindowedAppContext { + public: + GTKWindowedAppContext() = default; + ~GTKWindowedAppContext(); + + void NotifyUILoopOfPendingFunctions() override; + + void PlatformQuitFromUIThread() override; + + void RunMainGTKLoop(); + + private: + static gboolean PendingFunctionsSourceFunc(gpointer data); + + static gboolean QuitSourceFunc(gpointer data); + + std::mutex pending_functions_idle_pending_mutex_; + guint pending_functions_idle_pending_ = 0; + + guint quit_idle_pending_ = 0; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_ diff --git a/src/xenia/ui/windowed_app_context_win.cc b/src/xenia/ui/windowed_app_context_win.cc new file mode 100644 index 000000000..399a2afd4 --- /dev/null +++ b/src/xenia/ui/windowed_app_context_win.cc @@ -0,0 +1,130 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/windowed_app_context_win.h" + +#include + +#include "xenia/base/platform_win.h" + +namespace xe { +namespace ui { + +bool Win32WindowedAppContext::pending_functions_window_class_registered_; + +Win32WindowedAppContext::~Win32WindowedAppContext() { + if (pending_functions_hwnd_) { + DestroyWindow(pending_functions_hwnd_); + } +} + +bool Win32WindowedAppContext::Initialize() { + // Logging possibly not initialized in this function yet. + // Create the message-only window for executing pending functions - using a + // window instead of executing them between iterations so non-main message + // loops, such as Windows modals, can execute pending functions too. + static const WCHAR kPendingFunctionsWindowClassName[] = + L"XeniaPendingFunctionsWindowClass"; + if (!pending_functions_window_class_registered_) { + WNDCLASSEXW pending_functions_window_class = {}; + pending_functions_window_class.cbSize = + sizeof(pending_functions_window_class); + pending_functions_window_class.lpfnWndProc = PendingFunctionsWndProc; + pending_functions_window_class.hInstance = hinstance_; + pending_functions_window_class.lpszClassName = + kPendingFunctionsWindowClassName; + if (!RegisterClassExW(&pending_functions_window_class)) { + return false; + } + pending_functions_window_class_registered_ = true; + } + pending_functions_hwnd_ = CreateWindowExW( + 0, kPendingFunctionsWindowClassName, L"Xenia Pending Functions", + WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, hinstance_, this); + if (!pending_functions_hwnd_) { + return false; + } + return true; +} + +void Win32WindowedAppContext::NotifyUILoopOfPendingFunctions() { + while (!PostMessageW(pending_functions_hwnd_, + kPendingFunctionsWindowClassMessageExecute, 0, 0)) { + Sleep(1); + } +} + +void Win32WindowedAppContext::PlatformQuitFromUIThread() { + // Send WM_QUIT to whichever loop happens to process it - may be the loop of a + // built-in modal window, which is unaware of HasQuitFromUIThread, don't let + // it delay quitting indefinitely. + PostQuitMessage(EXIT_SUCCESS); +} + +int Win32WindowedAppContext::RunMainMessageLoop() { + int result = EXIT_SUCCESS; + MSG message; + // The HasQuitFromUIThread check is not absolutely required, but for + // additional safety in case WM_QUIT is not received for any reason. + while (!HasQuitFromUIThread()) { + BOOL message_result = GetMessageW(&message, nullptr, 0, 0); + if (message_result == 0 || message_result == -1) { + // WM_QUIT (0 - this is the primary message loop, no need to resend, also + // contains the result from PostQuitMessage in wParam) or an error + // (-1). Quitting the context will run the pending functions. Getting + // WM_QUIT doesn't imply that QuitFromUIThread has been called already, it + // may originate in some place other than PlatformQuitFromUIThread as + // well - call it to finish everything including the pending functions. + QuitFromUIThread(); + result = message_result ? EXIT_FAILURE : int(message.wParam); + break; + } + TranslateMessage(&message); + DispatchMessageW(&message); + } + return result; +} + +LRESULT CALLBACK Win32WindowedAppContext::PendingFunctionsWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + if (message == WM_CLOSE) { + // Need the window for the entire context's lifetime, don't allow anything + // to close it. + return 0; + } + if (message == WM_NCCREATE) { + SetWindowLongPtrW( + hwnd, GWLP_USERDATA, + reinterpret_cast( + reinterpret_cast(lparam)->lpCreateParams)); + } else { + auto app_context = reinterpret_cast( + GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + if (app_context) { + switch (message) { + case WM_DESTROY: + // The message-only window owned by the context is being destroyed, + // thus the context won't be able to execute pending functions + // anymore - can't continue functioning normally. + app_context->QuitFromUIThread(); + break; + case kPendingFunctionsWindowClassMessageExecute: + app_context->ExecutePendingFunctionsFromUIThread(); + break; + default: + break; + } + } + } + return DefWindowProcW(hwnd, message, wparam, lparam); +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/windowed_app_context_win.h b/src/xenia/ui/windowed_app_context_win.h new file mode 100644 index 000000000..1c01961ac --- /dev/null +++ b/src/xenia/ui/windowed_app_context_win.h @@ -0,0 +1,58 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_ +#define XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_ + +#include + +#include "xenia/base/platform_win.h" +#include "xenia/ui/windowed_app_context.h" + +namespace xe { +namespace ui { + +class Win32WindowedAppContext final : public WindowedAppContext { + public: + // Must call Initialize and check its result after creating to be able to + // perform pending function calls. + explicit Win32WindowedAppContext(HINSTANCE hinstance, int show_cmd) + : hinstance_(hinstance), show_cmd_(show_cmd) {} + ~Win32WindowedAppContext(); + + bool Initialize(); + + HINSTANCE hinstance() const { return hinstance_; } + int show_cmd() const { return show_cmd_; } + + void NotifyUILoopOfPendingFunctions() override; + + void PlatformQuitFromUIThread() override; + + int RunMainMessageLoop(); + + private: + enum : UINT { + kPendingFunctionsWindowClassMessageExecute = WM_USER, + }; + + static LRESULT CALLBACK PendingFunctionsWndProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam); + + HINSTANCE hinstance_; + int show_cmd_; + HWND pending_functions_hwnd_ = nullptr; + + static bool pending_functions_window_class_registered_; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_ diff --git a/src/xenia/ui/windowed_app_main_posix.cc b/src/xenia/ui/windowed_app_main_posix.cc new file mode 100644 index 000000000..60b93a04e --- /dev/null +++ b/src/xenia/ui/windowed_app_main_posix.cc @@ -0,0 +1,64 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include +#include +#include + +#include "xenia/base/cvar.h" +#include "xenia/base/logging.h" +#include "xenia/ui/windowed_app.h" +#include "xenia/ui/windowed_app_context_gtk.h" + +extern "C" int main(int argc_pre_gtk, char** argv_pre_gtk) { + // Initialize GTK+, which will handle and remove its own arguments from argv. + // Both GTK+ and Xenia use --option=value argument format (see man + // gtk-options), however, it's meaningless to try to parse the same argument + // both as a GTK+ one and as a cvar. Make GTK+ options take precedence in case + // of a name collision, as there's an alternative way of setting Xenia options + // (the config). + int argc_post_gtk = argc_pre_gtk; + char** argv_post_gtk = argv_pre_gtk; + if (!gtk_init_check(&argc_post_gtk, &argv_post_gtk)) { + // Logging has not been initialized yet. + std::fputs("Failed to initialize GTK+\n", stderr); + return EXIT_FAILURE; + } + + int result; + + { + xe::ui::GTKWindowedAppContext app_context; + + std::unique_ptr app = + xe::ui::GetWindowedAppCreator()(app_context); + + cvar::ParseLaunchArguments(argc_post_gtk, argv_post_gtk, + app->GetPositionalOptionsUsage(), + app->GetPositionalOptions()); + + // Initialize logging. Needs parsed cvars. + xe::InitializeLogging(app->GetName()); + + if (app->OnInitialize()) { + app_context.RunMainGTKLoop(); + result = EXIT_SUCCESS; + } else { + result = EXIT_FAILURE; + } + + app->InvokeOnDestroy(); + } + + // Logging may still be needed in the destructors. + xe::ShutdownLogging(); + + return result; +} diff --git a/src/xenia/ui/windowed_app_main_win.cc b/src/xenia/ui/windowed_app_main_win.cc new file mode 100644 index 000000000..114d36fc0 --- /dev/null +++ b/src/xenia/ui/windowed_app_main_win.cc @@ -0,0 +1,70 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include + +#include "xenia/base/console.h" +#include "xenia/base/cvar.h" +#include "xenia/base/main_win.h" +#include "xenia/base/platform_win.h" +#include "xenia/ui/windowed_app.h" +#include "xenia/ui/windowed_app_context_win.h" + +DEFINE_bool(enable_console, false, "Open a console window with the main window", + "General"); + +int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE hinstance_prev, + LPWSTR command_line, int show_cmd) { + int result; + + { + xe::ui::Win32WindowedAppContext app_context(hinstance, show_cmd); + // TODO(Triang3l): Initialize creates a window. Set DPI awareness via the + // manifest. + if (!app_context.Initialize()) { + return EXIT_FAILURE; + } + + std::unique_ptr app = + xe::ui::GetWindowedAppCreator()(app_context); + + if (!xe::ParseWin32LaunchArguments(false, app->GetPositionalOptionsUsage(), + app->GetPositionalOptions(), nullptr)) { + return EXIT_FAILURE; + } + + // TODO(Triang3l): Rework this, need to initialize the console properly, + // disable has_console_attached_ by default in windowed apps, and attach + // only if needed. + if (cvars::enable_console) { + xe::AttachConsole(); + } + + // Initialize COM on the UI thread with the apartment-threaded concurrency + // model, so dialogs can be used. + if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) { + return EXIT_FAILURE; + } + + xe::InitializeWin32App(app->GetName()); + + result = + app->OnInitialize() ? app_context.RunMainMessageLoop() : EXIT_FAILURE; + + app->InvokeOnDestroy(); + } + + // Logging may still be needed in the destructors. + xe::ShutdownWin32App(); + + CoUninitialize(); + + return result; +} diff --git a/src/xenia/vfs/premake5.lua b/src/xenia/vfs/premake5.lua index 94b666dce..f312d93c6 100644 --- a/src/xenia/vfs/premake5.lua +++ b/src/xenia/vfs/premake5.lua @@ -27,7 +27,7 @@ project("xenia-vfs-dump") files({ "vfs_dump.cc", - project_root.."/src/xenia/base/main_"..platform_suffix..".cc", + project_root.."/src/xenia/base/console_app_main_"..platform_suffix..".cc", }) resincludedirs({ project_root, diff --git a/src/xenia/vfs/vfs_dump.cc b/src/xenia/vfs/vfs_dump.cc index 98d8eb1b7..2bca814bb 100644 --- a/src/xenia/vfs/vfs_dump.cc +++ b/src/xenia/vfs/vfs_dump.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -11,8 +11,9 @@ #include #include +#include "xenia/base/console_app_main.h" +#include "xenia/base/cvar.h" #include "xenia/base/logging.h" -#include "xenia/base/main.h" #include "xenia/base/math.h" #include "xenia/vfs/devices/stfs_container_device.h" @@ -113,5 +114,5 @@ int vfs_dump_main(const std::vector& args) { } // namespace vfs } // namespace xe -DEFINE_ENTRY_POINT("xenia-vfs-dump", xe::vfs::vfs_dump_main, - "[source] [dump_path]", "source", "dump_path"); +XE_DEFINE_CONSOLE_APP("xenia-vfs-dump", xe::vfs::vfs_dump_main, + "[source] [dump_path]", "source", "dump_path"); diff --git a/tools/build/scripts/test_suite.lua b/tools/build/scripts/test_suite.lua index 563624c09..a83e891de 100644 --- a/tools/build/scripts/test_suite.lua +++ b/tools/build/scripts/test_suite.lua @@ -31,7 +31,7 @@ local function combined_test_suite(test_suite_name, project_root, base_path, con }) files({ project_root.."/"..build_tools_src.."/test_suite_main.cc", - project_root.."/src/xenia/base/main_"..platform_suffix..".cc", + project_root.."/src/xenia/base/console_app_main_"..platform_suffix..".cc", base_path.."/**_test.cc", }) filter("toolset:msc") diff --git a/tools/build/src/test_suite_main.cc b/tools/build/src/test_suite_main.cc index 1ea73ac36..1e34b4c8f 100644 --- a/tools/build/src/test_suite_main.cc +++ b/tools/build/src/test_suite_main.cc @@ -13,8 +13,8 @@ #include #include +#include "xenia/base/console_app_main.h" #include "xenia/base/cvar.h" -#include "xenia/base/main.h" #define CATCH_CONFIG_RUNNER #include "third_party/catch/single_include/catch2/catch.hpp" @@ -43,5 +43,5 @@ int test_suite_main(const std::vector& args) { #error XE_TEST_SUITE_NAME is undefined! #endif -DEFINE_ENTRY_POINT_TRANSPARENT(XE_TEST_SUITE_NAME, - xe::test_suite::test_suite_main); +XE_DEFINE_CONSOLE_APP_TRANSPARENT(XE_TEST_SUITE_NAME, + xe::test_suite::test_suite_main); From 64366979c7d934f79243078bb32947bc1a48c56b Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 28 Aug 2021 19:44:23 +0300 Subject: [PATCH 29/36] [UI] Make Xenia title start from a capital letter --- src/xenia/app/emulator_window.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index d8e792aa2..156946750 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -41,7 +41,7 @@ using xe::ui::MenuItem; using xe::ui::MouseEvent; using xe::ui::UIEvent; -const std::string kBaseTitle = "xenia"; +const std::string kBaseTitle = "Xenia"; EmulatorWindow::EmulatorWindow(Emulator* emulator, ui::WindowedAppContext& app_context) From 6986d6c7e8511bf402394959a525449f792d9713 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 28 Aug 2021 23:06:28 +0300 Subject: [PATCH 30/36] [Config] Use locale-neutral fmt instead of to_string --- src/xenia/base/cvar.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/xenia/base/cvar.h b/src/xenia/base/cvar.h index 6047b1bd8..1f92d0766 100644 --- a/src/xenia/base/cvar.h +++ b/src/xenia/base/cvar.h @@ -15,8 +15,9 @@ #include #include -#include "cpptoml/include/cpptoml.h" -#include "cxxopts/include/cxxopts.hpp" +#include "third_party/cpptoml/include/cpptoml.h" +#include "third_party/cxxopts/include/cxxopts.hpp" +#include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/filesystem.h" #include "xenia/base/string_util.h" @@ -216,7 +217,10 @@ inline std::string CommandVar::ToString( template std::string CommandVar::ToString(T val) { - return std::to_string(val); + // Use fmt::format instead of std::to_string for locale-neutral formatting of + // floats, always with a period rather than a comma, which is treated as an + // unidentified trailing character by cpptoml. + return fmt::format("{}", val); } template From e720e0a54059a0fb4b12a3993b2cbc63475d4006 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 5 Sep 2021 21:03:05 +0300 Subject: [PATCH 31/36] [Code] Remove game names from code comments (most of at least) --- src/xenia/apu/xma_context.cc | 7 +- src/xenia/cpu/backend/x64/x64_seq_vector.cc | 4 +- src/xenia/cpu/ppc/ppc_emit_altivec.cc | 3 +- src/xenia/gpu/command_processor.cc | 4 +- .../gpu/d3d12/d3d12_command_processor.cc | 13 ++-- src/xenia/gpu/d3d12/d3d12_command_processor.h | 4 +- .../gpu/d3d12/d3d12_render_target_cache.cc | 8 +-- src/xenia/gpu/d3d12/pipeline_cache.cc | 7 +- src/xenia/gpu/d3d12/texture_cache.cc | 10 +-- src/xenia/gpu/d3d12/texture_cache.h | 2 +- src/xenia/gpu/draw_util.cc | 13 ++-- src/xenia/gpu/draw_util.h | 12 ++-- src/xenia/gpu/dxbc_shader_translator.cc | 32 ++++----- src/xenia/gpu/dxbc_shader_translator_fetch.cc | 19 +++-- src/xenia/gpu/dxbc_shader_translator_om.cc | 5 +- src/xenia/gpu/gpu_flags.cc | 12 ++-- src/xenia/gpu/primitive_processor.cc | 18 ++--- src/xenia/gpu/registers.h | 26 +++---- src/xenia/gpu/render_target_cache.cc | 24 +++---- src/xenia/gpu/render_target_cache.h | 16 ++--- src/xenia/gpu/shader.h | 12 ++-- src/xenia/gpu/shader_translator.cc | 4 +- .../gpu/shaders/adaptive_triangle.hs.hlsl | 2 +- src/xenia/gpu/shaders/pixel_formats.hlsli | 8 +-- .../gpu/shaders/tessellation_adaptive.vs.hlsl | 4 +- .../gpu/shaders/texture_load_ctx1.cs.hlsl | 2 +- src/xenia/gpu/shared_memory.cc | 7 +- src/xenia/gpu/texture_conversion.cc | 3 +- src/xenia/gpu/texture_util.cc | 10 +-- src/xenia/gpu/texture_util.h | 49 +++++++------ src/xenia/gpu/ucode.h | 16 ++--- src/xenia/gpu/vulkan/buffer_cache.cc | 2 +- src/xenia/gpu/vulkan/pipeline_cache.cc | 2 +- src/xenia/gpu/xenos.h | 72 +++++++++---------- src/xenia/kernel/xam/apps/xgi_app.cc | 2 +- src/xenia/kernel/xam/apps/xlivebase_app.cc | 2 +- src/xenia/kernel/xam/content_manager.h | 4 +- src/xenia/kernel/xam/user_profile.cc | 5 +- src/xenia/kernel/xam/xam_net.cc | 8 ++- src/xenia/kernel/xam/xam_notify.cc | 4 +- src/xenia/kernel/xam/xam_party.cc | 3 +- src/xenia/kernel/xam/xam_user.cc | 2 +- src/xenia/kernel/xbdm/xbdm_misc.cc | 2 +- .../kernel/xboxkrnl/xboxkrnl_audio_xma.cc | 2 +- src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc | 4 +- src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc | 8 +-- src/xenia/kernel/xobject.h | 2 +- src/xenia/memory.cc | 11 ++- 48 files changed, 246 insertions(+), 245 deletions(-) diff --git a/src/xenia/apu/xma_context.cc b/src/xenia/apu/xma_context.cc index 9791cba85..a9662a0d8 100644 --- a/src/xenia/apu/xma_context.cc +++ b/src/xenia/apu/xma_context.cc @@ -811,12 +811,13 @@ void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel, // Loop through every sample, convert and drop it into the output array. // If more than one channel, we need to interleave the samples from each // channel next to each other. Always saturate because FFmpeg output is - // not limited to [-1, 1] (for example 1.095 as seen in RDR) + // not limited to [-1, 1] (for example 1.095 as seen in 5454082B). constexpr float scale = (1 << 15) - 1; auto out = reinterpret_cast(output_buffer); - // For testing of vectorized versions, stereo audio is common in Halo 3, since - // the first menu frame; the intro cutscene also has more than 2 channels. + // For testing of vectorized versions, stereo audio is common in 4D5307E6, + // since the first menu frame; the intro cutscene also has more than 2 + // channels. #if XE_ARCH_AMD64 static_assert(kSamplesPerFrame % 8 == 0); const auto in_channel_0 = reinterpret_cast(samples[0]); diff --git a/src/xenia/cpu/backend/x64/x64_seq_vector.cc b/src/xenia/cpu/backend/x64/x64_seq_vector.cc index 4c7fb665a..b5a8e49a2 100644 --- a/src/xenia/cpu/backend/x64/x64_seq_vector.cc +++ b/src/xenia/cpu/backend/x64/x64_seq_vector.cc @@ -1862,8 +1862,8 @@ struct PACK : Sequence> { src = i.src1; } // Saturate to [3,3....] so that only values between 3...[00] and 3...[FF] - // are valid - max before min to pack NaN as zero (Red Dead Redemption is - // heavily affected by the order - packs 0xFFFFFFFF in matrix code to get 0 + // are valid - max before min to pack NaN as zero (5454082B is heavily + // affected by the order - packs 0xFFFFFFFF in matrix code to get a 0 // constant). e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMM3333)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackD3DCOLORSat)); diff --git a/src/xenia/cpu/ppc/ppc_emit_altivec.cc b/src/xenia/cpu/ppc/ppc_emit_altivec.cc index 770def3c1..93f4df2b7 100644 --- a/src/xenia/cpu/ppc/ppc_emit_altivec.cc +++ b/src/xenia/cpu/ppc/ppc_emit_altivec.cc @@ -2069,7 +2069,8 @@ int InstrEmit_vpkd3d128(PPCHIRBuilder& f, const InstrData& i) { v = f.Pack(v, PACK_TYPE_FLOAT16_4); break; case 6: // VPACK_NORMPACKED64 4_20_20_20 w_z_y_x - // Used in 2K games like NBA 2K9, pretty rarely in general. + // Used in 54540829 and other installments in the series, pretty rarely in + // general. v = f.Pack(v, PACK_TYPE_ULONG_4202020); break; default: diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index 53af72115..2e1a43c36 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -738,7 +738,7 @@ bool CommandProcessor::ExecutePacketType3(RingBuffer* reader, uint32_t packet) { break; } case PM4_WAIT_FOR_IDLE: { - // This opcode is used by "Duke Nukem Forever" while going/being ingame + // This opcode is used by 5454084E while going / being ingame. assert_true(count == 1); uint32_t value = reader->ReadAndSwap(); XELOGGPU("GPU wait for idle = {:08X}", value); @@ -1168,7 +1168,7 @@ bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader, // and used to detect a finished query. bool is_end_via_z_pass = pSampleCounts->ZPass_A == kQueryFinished && pSampleCounts->ZPass_B == kQueryFinished; - // Older versions of D3D also checks for ZFail (First Gears of War) + // Older versions of D3D also checks for ZFail (4D5307D5). bool is_end_via_z_fail = pSampleCounts->ZFail_A == kQueryFinished && pSampleCounts->ZFail_B == kQueryFinished; std::memset(pSampleCounts, 0, sizeof(xe_gpu_depth_sample_counts)); diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index fca476673..39bdfb664 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -1662,7 +1662,7 @@ void D3D12CommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, gamma_ramp_upload_mapping_ + gamma_ramp_footprint.Offset); for (uint32_t i = 0; i < 256; ++i) { uint32_t value = gamma_ramp_.normal[i].value; - // Swap red and blue (Project Sylpheed has settings allowing separate + // Swap red and blue (535107D4 has settings allowing separate // configuration). mapping[i] = ((value & 1023) << 20) | (value & (1023 << 10)) | ((value >> 20) & 1023); @@ -2076,7 +2076,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, memexport_stream.index_count * memexport_format_size; // Try to reduce the number of shared memory operations when writing // different elements into the same buffer through different exports - // (happens in Halo 3). + // (happens in 4D5307E6). bool memexport_range_reused = false; for (uint32_t i = 0; i < memexport_range_count; ++i) { MemExportRange& memexport_range = memexport_ranges[i]; @@ -2878,8 +2878,9 @@ void D3D12CommandProcessor::UpdateSystemConstantValues( // Get the color info register values for each render target. Also, for ROV, // exclude components that don't exist in the format from the write mask. // Don't exclude fully overlapping render targets, however - two render - // targets with the same base address are used in the lighting pass of Halo 3, - // for example, with the needed one picked with dynamic control flow. + // targets with the same base address are used in the lighting pass of + // 4D5307E6, for example, with the needed one picked with dynamic control + // flow. reg::RB_COLOR_INFO color_infos[4]; float rt_clamp[4][4]; uint32_t rt_keep_masks[4][2]; @@ -2898,8 +2899,8 @@ void D3D12CommandProcessor::UpdateSystemConstantValues( } // Disable depth and stencil if it aliases a color render target (for - // instance, during the XBLA logo in Banjo-Kazooie, though depth writing is - // already disabled there). + // instance, during the XBLA logo in 58410954, though depth writing is already + // disabled there). bool depth_stencil_enabled = rb_depthcontrol.stencil_enable || rb_depthcontrol.z_enable; if (edram_rov_used && depth_stencil_enabled) { diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index 018c568d0..e4d810c41 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -83,9 +83,9 @@ class D3D12CommandProcessor : public CommandProcessor { // Gets the current color write mask, taking the pixel shader's write mask // into account. If a shader doesn't write to a render target, it shouldn't be - // written to and it shouldn't be even bound - otherwise, in Halo 3, one + // written to and it shouldn't be even bound - otherwise, in 4D5307E6, one // render target is being destroyed by a shader not writing anything, and in - // Banjo-Tooie, the result of clearing the top tile is being ignored because + // 58410955, the result of clearing the top tile is being ignored because // there are 4 render targets bound with the same EDRAM base (clearly not // correct usage), but the shader only clears 1, and then EDRAM buffer stores // conflict with each other. diff --git a/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc b/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc index 53df32683..1437e696a 100644 --- a/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc +++ b/src/xenia/gpu/d3d12/d3d12_render_target_cache.cc @@ -3619,7 +3619,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { case xenos::DepthRenderTargetFormat::kD24S8: // Round to the nearest even integer. This seems to be the correct, // adding +0.5 and rounding towards zero results in red instead of - // black in GTA IV and Halo 3 clear shaders. + // black in the 4D5307E6 clear shader. a.OpMul(dxbc::Dest::R(i, 0b1000), dxbc::Src::R(i, dxbc::Src::kWWWW), dxbc::Src::LF(float(0xFFFFFF))); a.OpRoundNE(dxbc::Dest::R(i, 0b1000), @@ -3804,7 +3804,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { case xenos::DepthRenderTargetFormat::kD24S8: // Round to the nearest even integer. This seems to be the correct, // adding +0.5 and rounding towards zero results in red instead of - // black in GTA IV and Halo 3 clear shaders. + // black in the 4D5307E6 clear shader. a.OpMul(dxbc::Dest::R(1, 0b1000), dxbc::Src::R(1, dxbc::Src::kWWWW), dxbc::Src::LF(float(0xFFFFFF))); a.OpRoundNE(dxbc::Dest::R(1, 0b1000), @@ -4181,7 +4181,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) { case xenos::DepthRenderTargetFormat::kD24S8: // Round to the nearest even integer. This seems to be the // correct, adding +0.5 and rounding towards zero results in red - // instead of black in GTA IV and Halo 3 clear shaders. + // instead of black in the 4D5307E6 clear shader. a.OpMul(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(0, dxbc::Src::kXXXX), dxbc::Src::LF(float(0xFFFFFF))); @@ -6228,7 +6228,7 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline( case xenos::DepthRenderTargetFormat::kD24S8: // Round to the nearest even integer. This seems to be the correct, // adding +0.5 and rounding towards zero results in red instead of - // black in GTA IV and Halo 3 clear shaders. + // black in the 4D5307E6 clear shader. a.OpMul(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(1, dxbc::Src::kXXXX), dxbc::Src::LF(float(0xFFFFFF))); a.OpRoundNE(dxbc::Dest::R(1, 0b0001), diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 556c7cacf..879bc8eee 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -1567,7 +1567,8 @@ bool PipelineCache::GetCurrentStateDescription( /* 16 */ PipelineBlendFactor::kSrcAlphaSat, }; // Like kBlendFactorMap, but with color modes changed to alpha. Some - // pipelines aren't created in Prey because a color mode is used for alpha. + // pipelines aren't created in 545407E0 because a color mode is used for + // alpha. static const PipelineBlendFactor kBlendFactorAlphaMap[32] = { /* 0 */ PipelineBlendFactor::kZero, /* 1 */ PipelineBlendFactor::kOne, @@ -1599,7 +1600,7 @@ bool PipelineCache::GetCurrentStateDescription( // have their sample count matching the one set in the pipeline - however if // we set NumRenderTargets to 0 and also disable depth / stencil, the sample // count must be set to 1 - while the command list may still have - // multisampled render targets bound (happens in Halo 3 main menu). + // multisampled render targets bound (happens in 4D5307E6 main menu). // TODO(Triang3l): Investigate interaction of OMSetRenderTargets with // non-null depth and DSVFormat DXGI_FORMAT_UNKNOWN in the same case. for (uint32_t i = 0; i < 4; ++i) { @@ -2005,7 +2006,7 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline( state_desc.BlendState.RenderTarget[i]; // Treat 1 * src + 0 * dest as disabled blending (there are opaque // surfaces drawn with blending enabled, but it's 1 * src + 0 * dest, in - // Call of Duty 4 - GPU performance is better when not blending. + // 415607E6 - GPU performance is better when not blending. if (rt.src_blend != PipelineBlendFactor::kOne || rt.dest_blend != PipelineBlendFactor::kZero || rt.blend_op != xenos::BlendOp::kAdd || diff --git a/src/xenia/gpu/d3d12/texture_cache.cc b/src/xenia/gpu/d3d12/texture_cache.cc index 17244c573..8f7fc0fb9 100644 --- a/src/xenia/gpu/d3d12/texture_cache.cc +++ b/src/xenia/gpu/d3d12/texture_cache.cc @@ -121,8 +121,8 @@ namespace shaders { // components of operands in shaders. // For DXT3A and DXT5A, RRRR swizzle is specified in: // http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf -// Halo 3 also expects replicated components in k_8 sprites. -// DXN is read as RG in Halo 3, but as RA in Call of Duty. +// 4D5307E6 also expects replicated components in k_8 sprites. +// DXN is read as RG in 4D5307E6, but as RA in 415607E6. // TODO(Triang3l): Find out the correct contents of unused texture components. const TextureCache::HostFormat TextureCache::host_formats_[64] = { // k_1_REVERSE @@ -250,9 +250,9 @@ const TextureCache::HostFormat TextureCache::host_formats_[64] = { LoadMode::kUnknown, {2, 1, 0, 3}}, // k_Y1_Cr_Y0_Cb_REP - // Used for videos in NBA 2K9. Red and blue must be swapped. + // Used for videos in 54540829. Red and blue must be swapped. // TODO(Triang3l): D3DFMT_G8R8_G8B8 is DXGI_FORMAT_R8G8_B8G8_UNORM * 255.0f, - // watch out for num_format int, division in shaders, etc., in NBA 2K9 it + // watch out for num_format int, division in shaders, etc., in 54540829 it // works as is. Also need to decompress if the size is uneven, but should be // a very rare case. {DXGI_FORMAT_R8G8_B8G8_UNORM, @@ -1309,7 +1309,7 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { // Clear the bindings not only for this draw call, but entirely, because // loading may be needed in some draw call later, which may have the same // key for some binding as before the invalidation, but texture_invalidated_ - // being false (menu background in Halo 3). + // being false (menu background in 4D5307E6). for (size_t i = 0; i < xe::countof(texture_bindings_); ++i) { texture_bindings_[i].Clear(); } diff --git a/src/xenia/gpu/d3d12/texture_cache.h b/src/xenia/gpu/d3d12/texture_cache.h index 5c9c049c4..21a100ea2 100644 --- a/src/xenia/gpu/d3d12/texture_cache.h +++ b/src/xenia/gpu/d3d12/texture_cache.h @@ -418,7 +418,7 @@ class TextureCache { // Uncompression info for when the regular host format for this texture is // block-compressed, but the size is not block-aligned, and thus such // texture cannot be created in Direct3D on PC and needs decompression, - // however, such textures are common, for instance, in Halo 3. This only + // however, such textures are common, for instance, in 4D5307E6. This only // supports unsigned normalized formats - let's hope GPUSIGN_SIGNED was not // used for DXN and DXT5A. DXGI_FORMAT dxgi_format_uncompressed; diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index 49149b7f9..5157c1a88 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -24,12 +24,13 @@ #include "xenia/gpu/texture_util.h" #include "xenia/gpu/xenos.h" +// Very prominent in 545407F2. DEFINE_bool( resolve_resolution_scale_duplicate_second_pixel, true, "When using resolution scale, apply the hack that duplicates the " "right/lower host pixel in the left and top sides of render target resolve " "areas to eliminate the gap caused by half-pixel offset (this is necessary " - "for certain games like GTA IV to work).", + "for certain games to display the scene graphics).", "GPU"); DEFINE_bool( @@ -952,11 +953,11 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, dest_dimension = xenos::DataDimension::k2DOrStacked; // RB_COPY_DEST_PITCH::copy_dest_height is the real texture height used // for 3D texture pitch, it's not relative to 0,0 of the coordinate space - // (in Halo 3, the sniper rifle scope has copy_dest_height of 192, but the - // rectangle's Y is 64...256) - provide the real height of the rectangle - // since 32x32 tiles are stored linearly anyway. In addition, the height - // in RB_COPY_DEST_PITCH may be larger than needed - in Red Dead - // Redemption, a UI texture for the letterbox bars alpha is located within + // (in 4D5307E6, the sniper rifle scope has copy_dest_height of 192, but + // the rectangle's Y is 64...256) - provide the real height of the + // rectangle since 32x32 tiles are stored linearly anyway. In addition, + // the height in RB_COPY_DEST_PITCH may be larger than needed - in + // 5454082B, a UI texture for the letterbox bars alpha is located within // the range of a 1280x720 resolve target, so with resolution scaling it's // also wrongly detected as scaled, while only 1280x208 is being resolved. dest_height = uint32_t(y1 - y0); diff --git a/src/xenia/gpu/draw_util.h b/src/xenia/gpu/draw_util.h index d23d0d25a..8aa7287f6 100644 --- a/src/xenia/gpu/draw_util.h +++ b/src/xenia/gpu/draw_util.h @@ -67,7 +67,7 @@ constexpr bool IsPrimitivePolygonal(bool vgt_output_path_is_tessellation_enable, // TODO(Triang3l): Investigate how kRectangleList should be treated - possibly // actually drawn as two polygons on the console, however, the current // geometry shader doesn't care about the winding order - allowing backface - // culling for rectangles currently breaks Gears of War 2. + // culling for rectangles currently breaks 4D53082D. return false; } @@ -112,10 +112,10 @@ constexpr float GetD3D10PolygonOffsetFactor( return float(1 << 24); } // 20 explicit + 1 implicit (1.) mantissa bits. - // 2^20 is not enough for Call of Duty 4 retail version's first mission F.N.G. - // shooting range floor (with the number 1) on Direct3D 12. Tested on Nvidia - // GeForce GTX 1070, the exact formula (taking into account the 0...1 to - // 0...0.5 remapping described below) used for testing is + // 2^20 is not enough for 415607E6 retail version's training mission shooting + // range floor (with the number 1) on Direct3D 12. Tested on Nvidia GeForce + // GTX 1070, the exact formula (taking into account the 0...1 to 0...0.5 + // remapping described below) used for testing is // `int(ceil(offset * 2^20 * 0.5)) * sign(offset)`. With 2^20 * 0.5, there // are various kinds of stripes dependending on the view angle in that // location. With 2^21 * 0.5, the issue is not present. @@ -141,7 +141,7 @@ inline bool DoesCoverageDependOnAlpha(reg::RB_COLORCONTROL rb_colorcontrol) { // pre-passes and shadowmaps. The shader must have its ucode analyzed. If // IsRasterizationPotentiallyDone, this shouldn't be called, and assumed false // instead. Helps reject the pixel shader in some cases - memexport draws in -// Halo 3, and also most of some 1-point draws not covering anything done for +// 4D5307E6, and also most of some 1-point draws not covering anything done for // some reason in different games with a leftover pixel shader from the previous // draw, but with SQ_PROGRAM_CNTL destroyed, reducing the number of // unpredictable unneeded translations of random shaders with different host diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index 30582b022..c66ad535f 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -23,11 +23,12 @@ #include "xenia/gpu/xenos.h" #include "xenia/ui/graphics_provider.h" +// The test case for AMD is 4D5307E6 (checked in 2018). DEFINE_bool(dxbc_switch, true, "Use switch rather than if for flow control. Turning this off or " "on may improve stability, though this heavily depends on the " "driver - on AMD, it's recommended to have this set to true, as " - "Halo 3 appears to crash when if is used for flow control " + "some titles appear to crash when if is used for flow control " "(possibly the shader compiler tries to flatten them). On Intel " "HD Graphics, this is ignored because of a crash with the switch " "instruction.", @@ -398,7 +399,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.xyz. - // ZYX swizzle according to Call of Duty 3 and Viva Pinata. + // ZYX swizzle according to 415607E1 and 4D5307F2. in_domain_location_used_ |= 0b0111; a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0111) : dxbc::Dest::R(0, 0b0111), @@ -425,7 +426,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { if (register_count() >= 1) { // Copy the domain location to r0.xyz. // ZYX swizzle with r1.y == 0, according to the water shader in - // Banjo-Kazooie: Nuts & Bolts. + // 4D5307ED. in_domain_location_used_ |= 0b0111; a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0111) : dxbc::Dest::R(0, 0b0111), @@ -447,10 +448,10 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { // appears that the tessellator offloads the reordering of coordinates // for edges to game shaders. // - // In Banjo-Kazooie: Nuts & Bolts, the water shader multiplies the - // first control point's position by r0.z, the second CP's by r0.y, - // and the third CP's by r0.x. But before doing that it swizzles - // r0.xyz the following way depending on the value in r1.y: + // In 4D5307ED, the water shader multiplies the first control point's + // position by r0.z, the second CP's by r0.y, and the third CP's by + // r0.x. But before doing that it swizzles r0.xyz the following way + // depending on the value in r1.y: // - ZXY for 1.0. // - YZX for 2.0. // - XZY for 4.0. @@ -478,9 +479,9 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0011) : dxbc::Dest::R(0, 0b0011), dxbc::Src::VDomain()); - // Control point indices according to the shader from the main menu of - // Defender, which starts from `cndeq r2, c255.xxxy, r1.xyzz, r0.zzzz`, - // where c255.x is 0, and c255.y is 1. + // Control point indices according the main menu of 58410823, with + // `cndeq r2, c255.xxxy, r1.xyzz, r0.zzzz` in the prologue of the + // shader, where c255.x is 0, and c255.y is 1. // r0.z for (1 - r0.x) * (1 - r0.y) // r1.x for r0.x * (1 - r0.y) // r1.y for r0.x * r0.y @@ -509,7 +510,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { assert_true(register_count() >= 2); if (register_count() >= 1) { // Copy the domain location to r0.yz. - // XY swizzle according to the ground shader in Viva Pinata. + // XY swizzle according to the ground shader in 4D5307F2. in_domain_location_used_ |= 0b0011; a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0110) : dxbc::Dest::R(0, 0b0110), @@ -530,9 +531,8 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() { // the tessellator offloads the reordering of coordinates for edges to // game shaders. // - // In Viva Pinata, if we assume that r0.y is U and r0.z is V, the - // factors each control point value is multiplied by are the - // following: + // In 4D5307F2, if we assume that r0.y is U and r0.z is V, the factors + // each control point value is multiplied by are the following: // - (1-u)*(1-v), u*(1-v), (1-u)*v, u*v for 0.0 (identity swizzle). // - u*(1-v), (1-u)*(1-v), u*v, (1-u)*v for 1.0 (YXWZ). // - u*v, (1-u)*v, u*(1-v), (1-u)*(1-v) for 2.0 (WZYX). @@ -1452,7 +1452,7 @@ void DxbcShaderTranslator::StoreResult(const InstructionResult& result, dest = dxbc::Dest::R(system_temp_point_size_edge_flag_kill_vertex_); break; case InstructionStorageTarget::kExportAddress: - // Validate memexport writes (Halo 3 has some weird invalid ones). + // Validate memexport writes (4D5307E6 has some completely invalid ones). if (!can_store_memexport_address || memexport_alloc_current_count_ == 0 || memexport_alloc_current_count_ > Shader::kMaxMemExports || system_temps_memexport_address_[memexport_alloc_current_count_ - 1] == @@ -1463,7 +1463,7 @@ void DxbcShaderTranslator::StoreResult(const InstructionResult& result, system_temps_memexport_address_[memexport_alloc_current_count_ - 1]); break; case InstructionStorageTarget::kExportData: { - // Validate memexport writes (Halo 3 has some weird invalid ones). + // Validate memexport writes (4D5307E6 has some completely invalid ones). if (memexport_alloc_current_count_ == 0 || memexport_alloc_current_count_ > Shader::kMaxMemExports || system_temps_memexport_data_[memexport_alloc_current_count_ - 1] diff --git a/src/xenia/gpu/dxbc_shader_translator_fetch.cc b/src/xenia/gpu/dxbc_shader_translator_fetch.cc index cb2d529da..4033239e0 100644 --- a/src/xenia/gpu/dxbc_shader_translator_fetch.cc +++ b/src/xenia/gpu/dxbc_shader_translator_fetch.cc @@ -705,10 +705,10 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( // Add a small epsilon to the offset (1.5/4 the fixed-point texture // coordinate ULP - shouldn't significantly effect the fixed-point // conversion; 1/4 is also not enough with 3x resolution scaling very - // noticeably on the weapon in Halo 3) to resolve ambiguity when fetching + // noticeably on the weapon in 4D5307E6) to resolve ambiguity when fetching // point-sampled textures between texels. This applies to both normalized - // (Banjo-Kazooie Xbox Live Arcade logo, coordinates interpolated between - // vertices with half-pixel offset) and unnormalized (Halo 3 lighting + // (58410954 Xbox Live Arcade logo, coordinates interpolated between + // vertices with half-pixel offset) and unnormalized (4D5307E6 lighting // G-buffer reading, ps_param_gen pixels) coordinates. On Nvidia Pascal, // without this adjustment, blockiness is visible in both cases. Possibly // there is a better way, however, an attempt was made to error-correct @@ -1595,13 +1595,12 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction( // - Data. - // Viva Pinata uses vertex displacement map textures for tessellated - // models like the beehive tree with explicit LOD with point sampling - // (they store values packed in two components), however, the fetch - // constant has anisotropic filtering enabled. However, Direct3D 12 - // doesn't allow mixing anisotropic and point filtering. Possibly - // anistropic filtering should be disabled when explicit LOD is used - do - // this here. + // 4D5307F2 uses vertex displacement map textures for tessellated models + // like the beehive tree with explicit LOD with point sampling (they store + // values packed in two components), however, the fetch constant has + // anisotropic filtering enabled. However, Direct3D 12 doesn't allow + // mixing anisotropic and point filtering. Possibly anistropic filtering + // should be disabled when explicit LOD is used - do this here. uint32_t sampler_binding_index = FindOrAddSamplerBinding( tfetch_index, instr.attributes.mag_filter, instr.attributes.min_filter, instr.attributes.mip_filter, diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index 1002bf14d..a49500ec2 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -287,8 +287,7 @@ void DxbcShaderTranslator::StartPixelShader_LoadROVParameters() { dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kYYYY)); // Choose in which 40-sample half of the tile the pixel is, for swapping // 40-sample columns when accessing the depth buffer - games expect this - // behavior when writing depth back to the EDRAM via color writing (GTA IV, - // Halo 3). + // behavior when writing depth back to the EDRAM via color writing (4D5307E6). // system_temp_rov_params_.x = tile-local sample 0 X >= 40 // system_temp_rov_params_.y = row offset // system_temp_rov_params_.z = X sample 0 position within the tile @@ -3282,7 +3281,7 @@ void DxbcShaderTranslator::ROV_DepthTo24Bit(uint32_t d24_temp, dxbc::Src::LF(float(0xFFFFFF))); // Round to the nearest even integer. This seems to be the correct way: // rounding towards zero gives 0xFF instead of 0x100 in clear shaders in, - // for instance, Halo 3, but other clear shaders in it are also broken if + // for instance, 4D5307E6, but other clear shaders in it are also broken if // 0.5 is added before ftou instead of round_ne. a_.OpRoundNE(d24_dest, d24_src); // Convert to fixed-point. diff --git a/src/xenia/gpu/gpu_flags.cc b/src/xenia/gpu/gpu_flags.cc index 5f73fd3c2..1510eeec0 100644 --- a/src/xenia/gpu/gpu_flags.cc +++ b/src/xenia/gpu/gpu_flags.cc @@ -28,16 +28,18 @@ DEFINE_bool( "the real reason why they're invalid is found.", "GPU"); +// Extremely bright screen borders in 4D5307E6. +// Reading between texels with half-pixel offset in 58410954. DEFINE_bool( half_pixel_offset, true, "Enable support of vertex half-pixel offset (D3D9 PA_SU_VTX_CNTL " "PIX_CENTER). Generally games are aware of the half-pixel offset, and " "having this enabled is the correct behavior (disabling this may " - "significantly break post-processing in some games, like Halo 3), but in " - "some games it might have been ignored, resulting in slight blurriness of " - "UI textures, for instance, when they are read between texels rather than " - "at texel centers (Banjo-Kazooie), or the leftmost/topmost pixels may not " - "be fully covered when MSAA is used with fullscreen passes.", + "significantly break post-processing in some games), but in certain games " + "it might have been ignored, resulting in slight blurriness of UI " + "textures, for instance, when they are read between texels rather than " + "at texel centers, or the leftmost/topmost pixels may not be fully covered " + "when MSAA is used with fullscreen passes.", "GPU"); DEFINE_int32(query_occlusion_fake_sample_count, 1000, diff --git a/src/xenia/gpu/primitive_processor.cc b/src/xenia/gpu/primitive_processor.cc index b00e4ce50..12ba9cbe5 100644 --- a/src/xenia/gpu/primitive_processor.cc +++ b/src/xenia/gpu/primitive_processor.cc @@ -57,7 +57,7 @@ DEFINE_bool( // TODO(Triang3l): More investigation of the cache threshold as cache lookups // and insertions require global critical region locking, and insertions also // require protecting pages. At 1024, the cache only made the performance worse -// (Tony Hawk's American Wasteland, 16-bit primitive reset index replacement). +// (415607D4, 16-bit primitive reset index replacement). DEFINE_int32( primitive_processor_cache_min_indices, 4096, "Smallest number of guest indices to store in the cache to try reusing " @@ -247,14 +247,14 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { // games using tessellated strips / fans so far. switch (tessellation_mode) { case xenos::TessellationMode::kDiscrete: - // - Call of Duty 3 - nets above barrels in the beginning of the - // first mission (turn right after the end of the intro) - + // - 415607E1 - nets above barrels in the beginning of the first + // mission (turn right after the end of the intro) - // kTriangleList. host_vertex_shader_type = Shader::HostVertexShaderType::kTriangleDomainCPIndexed; break; case xenos::TessellationMode::kContinuous: - // - Viva Pinata - tree building with a beehive in the beginning + // - 4D5307F2 - tree building with a beehive in the beginning // (visible on the start screen behind the logo), waterfall in the // beginning - kTriangleList. host_vertex_shader_type = @@ -276,7 +276,7 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { Shader::HostVertexShaderType::kQuadDomainCPIndexed; break; case xenos::TessellationMode::kContinuous: - // - Defender - retro screen and beams in the main menu - kQuadList. + // - 58410823 - retro screen and beams in the main menu - kQuadList. host_vertex_shader_type = Shader::HostVertexShaderType::kQuadDomainCPIndexed; break; @@ -285,14 +285,14 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { } break; case xenos::PrimitiveType::kTrianglePatch: - // - Banjo-Kazooie: Nuts & Bolts - water - adaptive. - // - Halo 3 - water - adaptive. + // - 4D5307E6 - water - adaptive. + // - 4D5307ED - water - adaptive. host_vertex_shader_type = Shader::HostVertexShaderType::kTriangleDomainPatchIndexed; break; case xenos::PrimitiveType::kQuadPatch: - // - Fable II - continuous. - // - Viva Pinata - garden ground - adaptive. + // - 4D5307F1 - continuous. + // - 4D5307F2 - garden ground - adaptive. host_vertex_shader_type = Shader::HostVertexShaderType::kQuadDomainPatchIndexed; break; diff --git a/src/xenia/gpu/registers.h b/src/xenia/gpu/registers.h index 2fc23b2c8..69d922d8b 100644 --- a/src/xenia/gpu/registers.h +++ b/src/xenia/gpu/registers.h @@ -335,10 +335,10 @@ union alignas(uint32_t) PA_SU_SC_MODE_CNTL { uint32_t cull_back : 1; // +1 // 0 - front is CCW, 1 - front is CW. uint32_t face : 1; // +2 - // The game Fuse uses poly_mode 2 for triangles, which is "reserved" on R6xx - // and not defined on Adreno 2xx, but polymode_front/back_ptype are 0 - // (points) in this case in Fuse, which should not be respected for - // non-kDualMode as the game wants to draw filled triangles. + // 4541096E uses poly_mode 2 for triangles, which is "reserved" on R6xx and + // not defined on Adreno 2xx, but polymode_front/back_ptype are 0 (points) + // in this case in 4541096E, which should not be respected for non-kDualMode + // as the title wants to draw filled triangles. xenos::PolygonModeEnable poly_mode : 2; // +3 xenos::PolygonType polymode_front_ptype : 3; // +5 xenos::PolygonType polymode_back_ptype : 3; // +8 @@ -559,16 +559,16 @@ union alignas(uint32_t) RB_COLORCONTROL { // (gl_FragCoord.y near 0 in the top, near 1 in the bottom here - D3D-like.) // For 2 samples, the top sample (closer to gl_FragCoord.y 0) is covered // when alpha is in [0.5, 1), the bottom sample is covered when the alpha is - // [1. With these thresholds, however, in Red Dead Redemption, almost all - // distant trees are transparent, this is asymmetric - fully transparent for - // a quarter of the range (or even half of the range for 2x and almost the - // entire range for 1x), but fully opaque only in one value. + // [1. With these thresholds, however, in 5454082B, almost all distant trees + // are transparent, this is asymmetric - fully transparent for a quarter of + // the range (or even half of the range for 2x and almost the entire range + // for 1x), but fully opaque only in one value. // Though, 2, 2, 2, 2 offset values are commonly used for undithered alpha - // to coverage (in games such as Red Dead Redemption, and overall in AMD - // driver implementations) - it appears that 2, 2, 2, 2 offsets are supposed - // to make this symmetric. - // Both Red Dead Redemption and RADV (which used AMDVLK as a reference) use - // 3, 1, 0, 2 offsets for dithered alpha to mask. + // to coverage (in games such as 5454082B, and overall in AMD driver + // implementations) - it appears that 2, 2, 2, 2 offsets are supposed to + // make this symmetric. + // Both 5454082B and RADV (which used AMDVLK as a reference) use 3, 1, 0, 2 + // offsets for dithered alpha to mask. // https://gitlab.freedesktop.org/nchery/mesa/commit/8a52e4cc4fad4f1c75acc0badd624778f9dfe202 // It appears that the offsets lower the thresholds by (offset / 4 / // sample count). That's consistent with both 2, 2, 2, 2 making the test diff --git a/src/xenia/gpu/render_target_cache.cc b/src/xenia/gpu/render_target_cache.cc index 76a3a2ef8..2dcf93789 100644 --- a/src/xenia/gpu/render_target_cache.cc +++ b/src/xenia/gpu/render_target_cache.cc @@ -40,6 +40,7 @@ DEFINE_bool( "reduce bandwidth usage during transfers as the previous depth won't need " "to be read.", "GPU"); +// The round trip is done, in particular, in 545407F2. DEFINE_string( depth_float24_conversion, "", "Method for converting 32-bit Z values to 20e4 floating point when using " @@ -56,8 +57,8 @@ DEFINE_string( " + Highest performance, allows early depth test and writing.\n" " + Host MSAA is possible with pixel-rate shading where supported.\n" " - EDRAM > RAM > EDRAM depth buffer round trip done in certain games " - "(such as GTA IV) destroys precision irreparably, causing artifacts if " - "another rendering pass is done after the EDRAM reupload.\n" + "destroys precision irreparably, causing artifacts if another rendering " + "pass is done after the EDRAM reupload.\n" " truncate:\n" " Convert to 20e4 directly in pixel shaders, always rounding down.\n" " + Average performance, conservative early depth test is possible.\n" @@ -96,18 +97,15 @@ DEFINE_bool( "bloom, etc., in some cases.", "GPU"); // Disabled by default because of full-screen effects that occur when game -// shaders assume piecewise linear, much more severe than blending-related -// issues. +// shaders assume piecewise linear (4541080F), much more severe than +// blending-related issues. DEFINE_bool( gamma_render_target_as_srgb, false, "When the host can't write piecewise linear gamma directly with correct " "blending, use sRGB output on the host for conceptually correct blending " - "in linear color space (to prevent issues such as bright squares around " - "bullet holes and overly dark lighting in Halo 3) while having slightly " - "different precision distribution in the render target and severely " - "incorrect values if the game accesses the resulting colors directly as " - "raw data (the whole screen in The Orange Box, for instance, since when " - "the first loading bar appears).", + "in linear color space while having slightly different precision " + "distribution in the render target and severely incorrect values if the " + "game accesses the resulting colors directly as raw data.", "GPU"); DEFINE_bool( mrt_edram_used_range_clamp_to_min, true, @@ -493,9 +491,9 @@ bool RenderTargetCache::Update(bool is_rasterization_done, // (issues caused by color and depth render target collisions haven't been // found yet), but render targets with smaller index are considered more // important - specifically, because of the usage in the lighting pass of - // Halo 3, which can be checked in the vertical look calibration sequence in + // 4D5307E6, which can be checked in the vertical look calibration sequence in // the beginning of the game: if render target 0 is removed in favor of 1, the - // UNSC servicemen and the world will be too dark, like fully in shadow - + // characters and the world will be too dark, like fully in shadow - // especially prominent on the helmet. This happens because the shader picks // between two render targets to write dynamically (though with a static, bool // constant condition), but all other state is set up in a way that implies @@ -624,7 +622,7 @@ bool RenderTargetCache::Update(bool is_rasterization_done, // "As if it was 64bpp" (contribution of 32bpp render targets multiplied by 2, // and clamping for 32bpp render targets divides this by 2) because 32bpp // render targets can be combined with twice as long 64bpp render targets. An - // example is the Dead Space 3 menu background (1-sample 1152x720, or 1200x720 + // example is the 4541099D menu background (1-sample 1152x720, or 1200x720 // after rounding to tiles, with a 32bpp depth buffer at 0 requiring 675 // tiles, and a 64bpp color buffer at 675 requiring 1350 tiles, but the // smallest distance between two render target bases is 675 tiles). diff --git a/src/xenia/gpu/render_target_cache.h b/src/xenia/gpu/render_target_cache.h index a3d580356..bf7c9a83e 100644 --- a/src/xenia/gpu/render_target_cache.h +++ b/src/xenia/gpu/render_target_cache.h @@ -70,10 +70,10 @@ class RenderTargetCache { // Significant differences: // - 8_8_8_8_GAMMA - the piecewise linear gamma curve is very different than // sRGB, one possible path is conversion in shaders (resulting in - // incorrect blending, especially visible on decals in Halo 3), another is - // using sRGB render targets and either conversion on resolve or reading - // the resolved data as a true sRGB texture (incorrect when the game - // accesses the data directly, like The Orange Box). + // incorrect blending, especially visible on decals in 4D5307E6), another + // is using sRGB render targets and either conversion on resolve or + // reading the resolved data as a true sRGB texture (incorrect when the + // game accesses the data directly, like 4541080F). // - 2_10_10_10_FLOAT - ranges significantly different than in float16, much // smaller RGB range, and alpha is fixed-point and has only 2 bits. // - 16_16, 16_16_16_16 - has -32 to 32 range, not -1 to 1 - need either to @@ -445,9 +445,9 @@ class RenderTargetCache { // aliasing naively, precision may be lost - host depth must only be // overwritten if the new guest value is different than the current host depth // when converted to the guest format (this catches the usual case of - // overwriting the depth buffer for clearing it mostly). Sonic the Hedgehog's - // intro cutscene, for example, has a good example of corruption that happens - // if this is not handled - the upper 1280x384 pixels are rendered in a very + // overwriting the depth buffer for clearing it mostly). 534507D6 intro + // cutscene, for example, has a good example of corruption that happens if + // this is not handled - the upper 1280x384 pixels are rendered in a very // "striped" way if the depth precision is lost (if this is made always return // false). virtual bool IsHostDepthEncodingDifferent( @@ -627,7 +627,7 @@ class RenderTargetCache { // surface info was changed), to avoid unneeded render target switching (which // is especially undesirable on tile-based GPUs) in the implementation if // simply disabling depth / stencil test or color writes and then re-enabling - // (Banjo-Kazooie does this often with color). Must also be used to determine + // (58410954 does this often with color). Must also be used to determine // whether it's safe to enable depth / stencil or writing to a specific color // render target in the pipeline for this draw call. // Only valid for non-pixel-shader-interlock paths. diff --git a/src/xenia/gpu/shader.h b/src/xenia/gpu/shader.h index f2f944155..f7c52cab0 100644 --- a/src/xenia/gpu/shader.h +++ b/src/xenia/gpu/shader.h @@ -551,7 +551,7 @@ struct ParsedAluInstruction { InstructionResult scalar_result; // Both operations must be executed before any result is stored if vector and // scalar operations are paired. There are cases of vector result being used - // as scalar operand or vice versa (the halo on Avalanche in Halo 3, for + // as scalar operand or vice versa (the ring on Avalanche in 4D5307E6, for // example), in this case there must be no dependency between the two // operations. @@ -851,11 +851,11 @@ class Shader { // highest static register address + 1, or 0 if no registers referenced this // way. SQ_PROGRAM_CNTL is not always reliable - some draws (like single point // draws with oPos = 0001 that are done by Xbox 360's Direct3D 9 sometimes; - // can be reproduced by launching Arrival in Halo 3 from the campaign lobby) - // that aren't supposed to cover any pixels use an invalid (zero) - // SQ_PROGRAM_CNTL, but with an outdated pixel shader loaded, in this case - // SQ_PROGRAM_CNTL may contain a number smaller than actually needed by the - // pixel shader - SQ_PROGRAM_CNTL should be used to go above this count if + // can be reproduced by launching the intro mission in 4D5307E6 from the + // campaign lobby) that aren't supposed to cover any pixels use an invalid + // (zero) SQ_PROGRAM_CNTL, but with an outdated pixel shader loaded, in this + // case SQ_PROGRAM_CNTL may contain a number smaller than actually needed by + // the pixel shader - SQ_PROGRAM_CNTL should be used to go above this count if // uses_register_dynamic_addressing is true. uint32_t register_static_address_bound() const { return register_static_address_bound_; diff --git a/src/xenia/gpu/shader_translator.cc b/src/xenia/gpu/shader_translator.cc index 8f6c61399..b1d1a060e 100644 --- a/src/xenia/gpu/shader_translator.cc +++ b/src/xenia/gpu/shader_translator.cc @@ -388,8 +388,8 @@ void Shader::GatherAluInstructionInformation( // allocation in shader translator implementations. // eA is (hopefully) always written to using: // mad eA, r#, const0100, c# - // (though there are some exceptions, shaders in Halo 3 for some reason set eA - // to zeros, but the swizzle of the constant is not .xyzw in this case, and + // (though there are some exceptions, shaders in 4D5307E6 for some reason set + // eA to zeros, but the swizzle of the constant is not .xyzw in this case, and // they don't write to eM#). // Export is done to vector_dest of the ucode instruction for both vector and // scalar operations - no need to check separately. diff --git a/src/xenia/gpu/shaders/adaptive_triangle.hs.hlsl b/src/xenia/gpu/shaders/adaptive_triangle.hs.hlsl index 31f7c36dd..c88563d79 100644 --- a/src/xenia/gpu/shaders/adaptive_triangle.hs.hlsl +++ b/src/xenia/gpu/shaders/adaptive_triangle.hs.hlsl @@ -36,7 +36,7 @@ XeHSConstantDataOutput XePatchConstant( // 2) r0.zyx -> r0.zyx by the guest (because r1.y is set to 0 by Xenia, which // apparently means identity swizzle to games). // 3) r0.z * v0 + r0.y * v1 + r0.x * v2 by the guest. - // With this order, there are no cracks in Halo 3 water. + // With this order, there are no cracks in 4D5307E6 water. [unroll] for (i = 0u; i < 3u; ++i) { output.edges[i] = xe_input_patch[(i + 1u) % 3u].edge_factor; } diff --git a/src/xenia/gpu/shaders/pixel_formats.hlsli b/src/xenia/gpu/shaders/pixel_formats.hlsli index 91bde0378..473a0620d 100644 --- a/src/xenia/gpu/shaders/pixel_formats.hlsli +++ b/src/xenia/gpu/shaders/pixel_formats.hlsli @@ -986,11 +986,11 @@ uint4 XeDXT3AAs1111TwoBlocksRowToBGRA4(uint2 halfblocks) { // DXT1/DXT3/DXT5 color components and CTX1 X/Y are ordered in: // http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf // (LSB on the right, MSB on the left.) - // TODO(Triang3l): Investigate this better, Halo: Reach is the only known game + // TODO(Triang3l): Investigate this better, 4D53085B is the only known game // that uses it (for lighting in certain places - one of easy to notice usages - // is the T-shaped (or somewhat H-shaped) metal beams in the beginning of - // Winter Contingency), however the contents don't say anything about the - // channel order. + // is the T-shaped (or somewhat H-shaped) metal beams in the beginning of the + // first mission), however the contents don't say anything about the channel + // order. uint4 row = (((halfblocks.xxyy >> uint2(3u, 11u).xyxy) & 1u) << 8u) | (((halfblocks.xxyy >> uint2(7u, 15u).xyxy) & 1u) << 24u) | (((halfblocks.xxyy >> uint2(2u, 10u).xyxy) & 1u) << 4u) | diff --git a/src/xenia/gpu/shaders/tessellation_adaptive.vs.hlsl b/src/xenia/gpu/shaders/tessellation_adaptive.vs.hlsl index fff6c501c..87ed1320a 100644 --- a/src/xenia/gpu/shaders/tessellation_adaptive.vs.hlsl +++ b/src/xenia/gpu/shaders/tessellation_adaptive.vs.hlsl @@ -5,8 +5,8 @@ XeHSControlPointInputAdaptive main(uint xe_edge_factor : SV_VertexID) { XeHSControlPointInputAdaptive output; // The Xbox 360's GPU accepts the float32 tessellation factors for edges // through a special kind of an index buffer. - // While Viva Pinata sets the factors to 0 for frustum-culled (quad) patches, - // in Halo 3 only allowing patches with factors above 0 makes distant + // While 4D5307F2 sets the factors to 0 for frustum-culled (quad) patches, in + // 4D5307E6 only allowing patches with factors above 0 makes distant // (triangle) patches disappear - it appears that there are no special values // for culled patches on the Xbox 360 (unlike zero, negative and NaN on // Direct3D 11). diff --git a/src/xenia/gpu/shaders/texture_load_ctx1.cs.hlsl b/src/xenia/gpu/shaders/texture_load_ctx1.cs.hlsl index eb908cc3a..6523cc92d 100644 --- a/src/xenia/gpu/shaders/texture_load_ctx1.cs.hlsl +++ b/src/xenia/gpu/shaders/texture_load_ctx1.cs.hlsl @@ -11,7 +11,7 @@ RWBuffer xe_texture_load_dest : register(u0); // Dword 1: // rrrrrrrrgggggggg // RRRRRRRRGGGGGGGG -// (R is in the higher bits, according to how this format is used in Halo 3). +// (R is in the higher bits, according to how this format is used in 4D5307E6). // Dword 2: // AA BB CC DD // EE FF GG HH diff --git a/src/xenia/gpu/shared_memory.cc b/src/xenia/gpu/shared_memory.cc index 0e47bd912..2b05821dc 100644 --- a/src/xenia/gpu/shared_memory.cc +++ b/src/xenia/gpu/shared_memory.cc @@ -465,9 +465,10 @@ std::pair SharedMemory::MemoryInvalidationCallback( // invalidated - if no GPU-written data nearby that was not intended to be // invalidated since it's not in sync with CPU memory and can't be // reuploaded. It's a lot cheaper to upload some excess data than to catch - // access violations - with 4 KB callbacks, the original Doom runs at 4 FPS - // on Intel Core i7-3770, with 64 KB the CPU game code takes 3 ms to run per - // frame, but with 256 KB it's 0.7 ms. + // access violations - with 4 KB callbacks, 58410824 (being a + // software-rendered game) runs at 4 FPS on Intel Core i7-3770, with 64 KB, + // the CPU game code takes 3 ms to run per frame, but with 256 KB, it's + // 0.7 ms. if (page_first & 63) { uint64_t gpu_written_start = system_page_flags_[block_first].valid_and_gpu_written; diff --git a/src/xenia/gpu/texture_conversion.cc b/src/xenia/gpu/texture_conversion.cc index bd028f47e..225e7feed 100644 --- a/src/xenia/gpu/texture_conversion.cc +++ b/src/xenia/gpu/texture_conversion.cc @@ -49,7 +49,8 @@ void CopySwapBlock(xenos::Endian endian, void* output, const void* input, void ConvertTexelCTX1ToR8G8(xenos::Endian endian, void* output, const void* input, size_t length) { // https://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf - // (R is in the higher bits, according to how this format is used in Halo 3). + // (R is in the higher bits, according to how this format is used in + // 4D5307E6). union { uint8_t data[8]; struct { diff --git a/src/xenia/gpu/texture_util.cc b/src/xenia/gpu/texture_util.cc index bd7a78c6c..e64e4c205 100644 --- a/src/xenia/gpu/texture_util.cc +++ b/src/xenia/gpu/texture_util.cc @@ -352,11 +352,11 @@ TextureGuestLayout GetGuestTextureLayout( xenos::kTextureSubresourceAlignmentBytes); // Estimate the memory amount actually referenced by the texture, which may - // be smaller (especially in the 1280x720 linear k_8_8_8_8 case in Ridge - // Racer Unbounded, for which memory exactly for 1280x720 is allocated, and - // aligning the height to 32 would cause access of an unallocated page) or - // bigger than the stride. For tiled textures, this is the dimensions - // aligned to 32x32x4 blocks (or x1 for the missing dimensions). + // be smaller (especially in the 1280x720 linear k_8_8_8_8 case in 4E4D083E, + // for which memory exactly for 1280x720 is allocated, and aligning the + // height to 32 would cause access of an unallocated page) or bigger than + // the stride. For tiled textures, this is the dimensions aligned to 32x32x4 + // blocks (or x1 for the missing dimensions). uint32_t level_width_blocks = xe::align(std::max(width_texels >> level, uint32_t(1)), format_info->block_width) / diff --git a/src/xenia/gpu/texture_util.h b/src/xenia/gpu/texture_util.h index 3d9bc0e99..733b3e88f 100644 --- a/src/xenia/gpu/texture_util.h +++ b/src/xenia/gpu/texture_util.h @@ -64,14 +64,14 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, // implies 32-block alignment for both uncompressed and compressed textures) // stored in the fetch constant, and height aligned to 32 blocks for Z slice // and array layer stride calculation purposes. The pitch can be different -// from the actual width - an example is Plants vs. Zombies, using 1408 pitch -// for a 1280x menu background). +// from the actual width - an example is 584109FF, using 1408 pitch for a +// 1280x menu background). // - The mip levels use `max(next_pow2(width or height in texels) >> level, 1)` // aligned to 32 blocks for the same purpose, likely disregarding the pitch // from the fetch constant. // // There is also mip tail packing if the fetch constant specifies that packed -// mips are enabled, for both tiled and linear textures (Prey uses linear +// mips are enabled, for both tiled and linear textures (545407E0 uses linear // DXT-compressed textures with packed mips very extensively for the game world // materials). In this case, mips with width or height of 16 or smaller are // stored not individually, but instead, in 32-texel (note: not 32-block - mip @@ -99,7 +99,7 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, // tail, and the offset calculation function doesn't have level == 0 checks in // it, only early-out if level < packed tail level (which can be 0). There are // examples of textures with packed base, for example, in the intro level of -// Prey (8x8 linear DXT1 - pairs of orange lights in the bottom of gambling +// 545407E0 (8x8 linear DXT1 - pairs of orange lights in the bottom of gambling // machines). // // Linear texture rows are aligned to 256 bytes, for both the base and the mips @@ -107,22 +107,21 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth, // fetch constant). // // However, all the 32x32x4 padding, being just padding, is not necessarily -// being actually accessed, especially for linear textures. Ridge Racer -// Unbounded has a 1280x720 k_8_8_8_8 linear texture, and allocates memory for -// exactly 1280x720, so aligning the height to 32 to 1280x736 results in access -// violations. So, while for stride calculations all the padding must be -// respected, for actual memory loads it's better to avoid trying to access it -// when possible: +// being actually accessed, especially for linear textures. 4E4D083E has a +// 1280x720 k_8_8_8_8 linear texture, and allocates memory for exactly 1280x720, +// so aligning the height to 32 to 1280x736 results in access violations. So, +// while for stride calculations all the padding must be respected, for actual +// memory loads it's better to avoid trying to access it when possible: // - If the pitch is bigger than the width, it's better to calculate the last // row's length from the width rather than the pitch (this also possibly works // in the other direction though - pitch < width is a weird situation, but // probably legal, and may lead to reading data from beyond the calculated // subresource stride). -// - For linear textures (like that 1280x720 example from Ridge Racer -// Unbounded), it's easy to calculate the exact memory extent that may be -// accessed knowing the dimensions (unlike for tiled textures with complex -// addressing within 32x32x4-block tiles), so there's no need to align them to -// 32x32x4 for memory extent calculation. +// - For linear textures (like that 1280x720 example from 4E4D083E), it's easy +// to calculate the exact memory extent that may be accessed knowing the +// dimensions (unlike for tiled textures with complex addressing within +// 32x32x4-block tiles), so there's no need to align them to 32x32x4 for +// memory extent calculation. // - For the linear packed mip tail, the extent can be calculated as max of // (block offsets + block extents) of all levels stored in it. // @@ -152,16 +151,16 @@ struct TextureGuestLayout { // tiled textures, this will be rounded to 32x32x4 blocks (or 32x32x1 // depending on the dimension), but for the linear subresources, this may be // significantly (including less 4 KB pages) smaller than the aligned size - // (like for Ridge Racer Unbounded where aligning the height of a 1280x720 - // linear texture results in access violations). For the linear mip tail, - // this includes all the mip levels stored in it. If the width is bigger - // than the pitch, this will also be taken into account for the last row so - // all memory actually used by the texture will be loaded, and may be bigger - // than the distance between array slices or levels. The purpose of this - // parameter is to make the memory amount that needs to be resident as close - // to the real amount as possible, to make sure all the needed data will be - // read, but also, if possible, unneeded memory pages won't be accessed - // (since that may trigger an access violation on the CPU). + // (like for 4E4D083E where aligning the height of a 1280x720 linear texture + // results in access violations). For the linear mip tail, this includes all + // the mip levels stored in it. If the width is bigger than the pitch, this + // will also be taken into account for the last row so all memory actually + // used by the texture will be loaded, and may be bigger than the distance + // between array slices or levels. The purpose of this parameter is to make + // the memory amount that needs to be resident as close to the real amount + // as possible, to make sure all the needed data will be read, but also, if + // possible, unneeded memory pages won't be accessed (since that may trigger + // an access violation on the CPU). uint32_t x_extent_blocks; uint32_t y_extent_blocks; uint32_t z_extent; diff --git a/src/xenia/gpu/ucode.h b/src/xenia/gpu/ucode.h index a4a169eb0..9d5ed7dca 100644 --- a/src/xenia/gpu/ucode.h +++ b/src/xenia/gpu/ucode.h @@ -483,7 +483,7 @@ enum class FetchOpcode : uint32_t { // - 3D (used for both 3D and stacked 2D texture): U, V, W (normalized or // unnormalized - same for both 3D W and stack layer; also VolMagFilter / // VolMinFilter between stack layers is supported, used for color correction - // in Burnout Revenge). + // in 454107DC). // - Cube: SC, TC (between 1 and 2 for normalized), face ID (0.0 to 5.0), the // cube vector ALU instruction is used to calculate them. // https://gpuopen.com/learn/fetching-from-cubes-and-octahedrons/ @@ -495,9 +495,9 @@ enum class FetchOpcode : uint32_t { // The total LOD for a sample is additive and is based on what is enabled. // // For cube maps, according to what texCUBEgrad compiles to in a modified - // HLSL shader of Brave: A Warrior's Tale and to XNA assembler output for PC - // SM3 texldd, register gradients are in cube space (not in SC/TC space, - // unlike the coordinates themselves). This isn't true for the GCN, however. + // HLSL shader of 455607D1 and to XNA assembler output for PC SM3 texldd, + // register gradients are in cube space (not in SC/TC space, unlike the + // coordinates themselves). This isn't true for the GCN, however. // // TODO(Triang3l): Find if gradients are unnormalized for cube maps if // coordinates are unnormalized. Since texldd doesn't perform any @@ -814,8 +814,8 @@ static_assert_size(TextureFetchInstruction, sizeof(uint32_t) * 3); // (mul, mad, dp, etc.) and for NaN in min/max. It's very important to respect // this rule for multiplication, as games often rely on it in vector // normalization (rcp and mul), Infinity * 0 resulting in NaN breaks a lot of -// things in games - causes white screen in Halo 3, white specular on -// characters in GTA IV. The result is always positive zero in this case, no +// things in games - causes white screen in 4D5307E6, white specular on +// characters in 545407F2. The result is always positive zero in this case, no // matter what the signs of the other operands are, according to R5xx // Acceleration section 8.7.5 "Legacy multiply behavior" and testing on // Adreno 200. This means that the following need to be taken into account @@ -1628,8 +1628,8 @@ enum class ExportRegister : uint32_t { // X - PSIZE (gl_PointSize). // Y - EDGEFLAG (glEdgeFlag) for PrimitiveType::kPolygon wireframe/point // drawing. - // Z - KILLVERTEX flag (used in Banjo-Kazooie: Nuts & Bolts for grass), set - // for killing primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition. + // Z - KILLVERTEX flag (used in 4D5307ED for grass), set for killing + // primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition. kVSPointSizeEdgeFlagKillVertex = 63, kPSColor0 = 0, diff --git a/src/xenia/gpu/vulkan/buffer_cache.cc b/src/xenia/gpu/vulkan/buffer_cache.cc index a02d9b54e..426fad033 100644 --- a/src/xenia/gpu/vulkan/buffer_cache.cc +++ b/src/xenia/gpu/vulkan/buffer_cache.cc @@ -507,7 +507,7 @@ std::pair BufferCache::UploadVertexBuffer( uint32_t upload_size = source_length; // Ping the memory subsystem for allocation size. - // TODO(DrChat): Artifacting occurring in GripShift with this enabled. + // TODO(DrChat): Artifacting occurring in 5841089E with this enabled. // physical_heap->QueryBaseAndSize(&upload_base, &upload_size); assert(upload_base <= source_addr); uint32_t source_offset = source_addr - upload_base; diff --git a/src/xenia/gpu/vulkan/pipeline_cache.cc b/src/xenia/gpu/vulkan/pipeline_cache.cc index 4defbcf04..128c5b133 100644 --- a/src/xenia/gpu/vulkan/pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/pipeline_cache.cc @@ -758,7 +758,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer, depth_bias_scale = depth_bias_scales[1]; depth_bias_offset = depth_bias_offsets[1]; } - // Convert to Vulkan units based on the values in Call of Duty 4: + // Convert to Vulkan units based on the values in 415607E6: // r_polygonOffsetScale is -1 there, but 32 in the register. // r_polygonOffsetBias is -1 also, but passing 2/65536. // 1/65536 and 2 scales are applied separately, however, and for shadow maps diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index d3a202dd6..2a0b6c938 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -235,10 +235,10 @@ enum class SurfaceNumFormat : uint32_t { // // Depth surfaces are also stored as 32bpp tiles, however, as opposed to color // surfaces, 40x16-sample halves of each tile are swapped - game shaders (for -// example, in GTA IV, Halo 3) perform this swapping when writing specific -// depth/stencil values by drawing to a depth buffer's memory through a color -// render target (to reupload a depth/stencil surface previously evicted from -// the EDRAM to the main memory, for instance). +// example, in 4D5307E6 main menu, 545407F2) perform this swapping when writing +// specific depth/stencil values by drawing to a depth buffer's memory through a +// color render target (to reupload a depth/stencil surface previously evicted +// from the EDRAM to the main memory, for instance). enum class MsaaSamples : uint32_t { k1X = 0, @@ -728,12 +728,12 @@ enum class SampleControl : uint32_t { // - sample_control is SQ_CONTEXT_MISC::sc_sample_cntl. // - interpolator_control_sampling_pattern is // SQ_INTERPOLATOR_CNTL::sampling_pattern. -// Centroid interpolation can be tested in Red Dead Redemption. If the GPU host -// backend implements guest MSAA properly, using host MSAA, with everything -// interpolated at centers, the Diez Coronas start screen background may have -// a few distinctly bright pixels on the mesas/buttes, where extrapolation -// happens. Interpolating certain values (ones that aren't used for gradient -// calculation, not texture coordinates) at centroids fixes this issue. +// Centroid interpolation can be tested in 5454082B. If the GPU host backend +// implements guest MSAA properly, using host MSAA, with everything interpolated +// at centers, the Monument Valley start screen background may have a few +// distinctly bright pixels on the mesas/buttes, where extrapolation happens. +// Interpolating certain values (ones that aren't used for gradient calculation, +// not texture coordinates) at centroids fixes this issue. inline uint32_t GetInterpolatorSamplingPattern( MsaaSamples msaa_samples, SampleControl sample_control, uint32_t interpolator_control_sampling_pattern) { @@ -763,9 +763,9 @@ enum class TessellationMode : uint32_t { enum class PolygonModeEnable : uint32_t { kDisabled = 0, // Render triangles. kDualMode = 1, // Send 2 sets of 3 polygons with the specified polygon type. - // The game Fuse uses 2 for triangles, which is "reserved" on R6xx and not - // defined on Adreno 2xx, but polymode_front/back_ptype are 0 (points) in this - // case in Fuse, which should not be respected for non-kDualMode as the game + // 4541096E uses 2 for triangles, which is "reserved" on R6xx and not defined + // on Adreno 2xx, but polymode_front/back_ptype are 0 (points) in this case in + // 4541096E, which should not be respected for non-kDualMode as the title // wants to draw filled triangles. }; @@ -785,17 +785,15 @@ enum class ModeControl : uint32_t { // for it especially since the Xbox 360 doesn't have early per-sample depth / // stencil, only early hi-Z / hi-stencil, and other registers possibly // toggling pixel shader execution are yet to be found): - // - Most of depth pre-pass draws in Call of Duty 4 use the kDepth more with - // a `oC0 = tfetch2D(tf0, r0.xy) * r1` shader, some use `oC0 = r0` though. + // - Most of depth pre-pass draws in 415607E6 use the kDepth more with a + // `oC0 = tfetch2D(tf0, r0.xy) * r1` shader, some use `oC0 = r0` though. // However, when alphatested surfaces are drawn, kColorDepth is explicitly // used with the same shader performing the texture fetch. - // - Red Dead Redemption has some kDepth draws with alphatest enabled, but the - // shader is `oC0 = r0`, which makes no sense (alphatest based on an - // interpolant from the vertex shader) as no texture alpha cutout is - // involved. - // - Red Dead Redemption also has kDepth draws with pretty complex shaders - // clearly for use only in the color pass - even fetching and filtering a - // shadowmap. + // - 5454082B has some kDepth draws with alphatest enabled, but the shader is + // `oC0 = r0`, which makes no sense (alphatest based on an interpolant from + // the vertex shader) as no texture alpha cutout is involved. + // - 5454082B also has kDepth draws with pretty complex shaders clearly for + // use only in the color pass - even fetching and filtering a shadowmap. // For now, based on these, let's assume the pixel shader is never used with // kDepth. kDepth = 5, @@ -833,10 +831,10 @@ enum class ModeControl : uint32_t { // coordinates of the corners). // // The rectangle is used for both the source render target and the destination -// texture, according to how it's used in Tales of Vesperia. +// texture, according to how it's used in 4E4D07E9. // // Direct3D 9 gives the rectangle in source render target coordinates (for -// example, in Halo 3, the sniper rifle scope has a (128,64)->(448,256) +// example, in 4D5307E6, the sniper rifle scope has a (128,64)->(448,256) // rectangle). It doesn't adjust the EDRAM base pointer, otherwise (taking into // account that 4x MSAA is used for the scope) it would have been // (8,0)->(328,192), but it's not. However, it adjusts the destination texture @@ -851,7 +849,7 @@ enum class ModeControl : uint32_t { // RB_COPY_DEST_PITCH's purpose appears to be not clamping or something like // that, but just specifying pitch for going between rows, and height for going // between 3D texture slices. copy_dest_pitch is rounded to 32 by Direct3D 9, -// copy_dest_height is not. In the Halo 3 sniper rifle scope example, +// copy_dest_height is not. In the 4D5307E6 sniper rifle scope example, // copy_dest_pitch is 320, and copy_dest_height is 192 - the same as the resolve // rectangle size (resolving from a 320x192 portion of the surface at 128,64 to // the whole texture, at 0,0). Relative to RB_COPY_DEST_BASE, the height should @@ -860,17 +858,17 @@ enum class ModeControl : uint32_t { // of the register) that it exists purely to be able to go between 3D texture // slices. // -// Window scissor must also be applied - in the jigsaw puzzle in Banjo-Tooie, -// there are 1280x720 resolve rectangles, but only the scissored 1280x256 -// needs to be copied, otherwise it overflows even beyond the EDRAM, and the -// depth buffer is visible on the screen. It also ensures the coordinates are -// not negative (in F.E.A.R., for example, the right tile is resolved with -// vertices (-640,0)->(640,720), however, the destination texture pointer is -// adjusted properly to the right half of the texture, and the source render -// target has a pitch of 800). +// Window scissor must also be applied - in the jigsaw puzzle in 58410955, there +// are 1280x720 resolve rectangles, but only the scissored 1280x256 needs to be +// copied, otherwise it overflows even beyond the EDRAM, and the depth buffer is +// visible on the screen. It also ensures the coordinates are not negative (in +// 565507D9, for example, the right tile is resolved with vertices +// (-640,0)->(640,720), however, the destination texture pointer is adjusted +// properly to the right half of the texture, and the source render target has a +// pitch of 800). // Granularity of offset and size in resolve operations is 8x8 pixels -// (GPU_RESOLVE_ALIGNMENT - for example, Halo 3 resolves a 24x16 region for a +// (GPU_RESOLVE_ALIGNMENT - for example, 4D5307E6 resolves a 24x16 region for a // 18x10 texture, 8x8 region for a 1x1 texture). // https://github.com/jmfauvel/CSGO-SDK/blob/master/game/client/view.cpp#L944 // https://github.com/stanriders/hl2-asw-port/blob/master/src/game/client/vgui_int.cpp#L901 @@ -1072,9 +1070,9 @@ union alignas(uint32_t) xe_gpu_texture_fetch_t { // pitch is irrelevant to them (but the 256-byte alignment requirement still // applies to linear textures). // Examples of pitch > aligned width: - // - Plants vs. Zombies (loading screen and menu backgrounds, 1408 for a - // 1280x linear k_DXT4_5 texture, which corresponds to 22 * 256 bytes - // rather than 20 * 256 for just 1280x). + // - 584109FF (loading screen and menu backgrounds, 1408 for a 1280x linear + // k_DXT4_5 texture, which corresponds to 22 * 256 bytes rather than + // 20 * 256 for just 1280x). uint32_t pitch : 9; // +22 uint32_t tiled : 1; // +31 diff --git a/src/xenia/kernel/xam/apps/xgi_app.cc b/src/xenia/kernel/xam/apps/xgi_app.cc index 771341cdb..8fdf699f4 100644 --- a/src/xenia/kernel/xam/apps/xgi_app.cc +++ b/src/xenia/kernel/xam/apps/xgi_app.cc @@ -98,7 +98,7 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, return X_E_SUCCESS; } case 0x000B0014: { - // Gets Jetpac XBLA in game + // Gets 584107FB in game. // get high score table? XELOGD("XGI_unknown"); return X_STATUS_SUCCESS; diff --git a/src/xenia/kernel/xam/apps/xlivebase_app.cc b/src/xenia/kernel/xam/apps/xlivebase_app.cc index 45440fa55..57bfc4fd4 100644 --- a/src/xenia/kernel/xam/apps/xlivebase_app.cc +++ b/src/xenia/kernel/xam/apps/xlivebase_app.cc @@ -66,7 +66,7 @@ X_HRESULT XLiveBaseApp::DispatchMessageSync(uint32_t message, return X_E_FAIL; } case 0x00058046: { - // Required to be successful for Forza 4 to detect signed-in profile + // Required to be successful for 4D530910 to detect signed-in profile // Doesn't seem to set anything in the given buffer, probably only takes // input XELOGD("XLiveBaseUnk58046({:08X}, {:08X}) unimplemented", buffer_ptr, diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index 4876bfd1c..0db9f0cb5 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -73,8 +73,8 @@ struct XCONTENT_DATA { } void set_display_name(const std::u16string_view value) { - // Some games (eg Goldeneye XBLA) require multiple null-terminators for it - // to read the string properly, blanking the array should take care of that + // Some games (e.g. 584108A9) require multiple null-terminators for it to + // read the string properly, blanking the array should take care of that std::fill_n(display_name_raw.chars, countof(display_name_raw.chars), 0); string_util::copy_and_swap_truncating(display_name_raw.chars, value, diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 4982a02c6..3bfa40f48 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -20,9 +20,8 @@ namespace kernel { namespace xam { UserProfile::UserProfile() { - // NeoGeo Battle Coliseum checks the user XUID against a mask of - // 0x00C0000000000000 (3<<54), if non-zero, it prevents the user from playing - // the game. + // 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54), + // if non-zero, it prevents the user from playing the game. // "You do not have permissions to perform this operation." xuid_ = 0xB13EBABEBABEBABE; name_ = "User"; diff --git a/src/xenia/kernel/xam/xam_net.cc b/src/xenia/kernel/xam/xam_net.cc index dbdff9dc1..413639156 100644 --- a/src/xenia/kernel/xam/xam_net.cc +++ b/src/xenia/kernel/xam/xam_net.cc @@ -249,8 +249,10 @@ dword_result_t NetDll_WSAStartup(dword_t caller, word_t version, data_ptr->max_sockets = wsaData.iMaxSockets; data_ptr->max_udpdg = wsaData.iMaxUdpDg; - // Some games (PoG) want this value round-tripped - they'll compare if it - // changes and bugcheck if it does. + // Some games (5841099F) want this value round-tripped - they'll compare if + // it changes and bugcheck if it does. + // TODO(Triang3l): Verify if the title ID in the comment is correct - added + // by benvanik as an acronym initially. uint32_t vendor_ptr = xe::load_and_swap(data_out + 0x190); xe::store_and_swap(data_out + 0x190, vendor_ptr); } @@ -459,7 +461,7 @@ dword_result_t NetDll_XNetGetTitleXnAddr(dword_t caller, // TODO(gibbed): A proper mac address. // RakNet's 360 version appears to depend on abEnet to create "random" 64-bit // numbers. A zero value will cause RakPeer::Startup to fail. This causes - // Peggle 2 to crash on startup. + // 58411436 to crash on startup. // The 360-specific code is scrubbed from the RakNet repo, but there's still // traces of what it's doing which match the game code. // https://github.com/facebookarchive/RakNet/blob/master/Source/RakPeer.cpp#L382 diff --git a/src/xenia/kernel/xam/xam_notify.cc b/src/xenia/kernel/xam/xam_notify.cc index 32fb2635d..6d09df7f3 100644 --- a/src/xenia/kernel/xam/xam_notify.cc +++ b/src/xenia/kernel/xam/xam_notify.cc @@ -79,8 +79,8 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id, } *id_ptr = dequeued ? id : 0; - // param_ptr may be null - Ghost Recon Advanced Warfighter 2 Demo explicitly - // passes nullptr in the code. + // param_ptr may be null - 555307F0 Demo explicitly passes nullptr in the + // code. // https://github.com/xenia-project/xenia/pull/1577 if (param_ptr) { *param_ptr = dequeued ? param : 0; diff --git a/src/xenia/kernel/xam/xam_party.cc b/src/xenia/kernel/xam/xam_party.cc index fd0556baa..5476f0b33 100644 --- a/src/xenia/kernel/xam/xam_party.cc +++ b/src/xenia/kernel/xam/xam_party.cc @@ -17,8 +17,7 @@ namespace kernel { namespace xam { dword_result_t XamPartyGetUserList(dword_t player_count, lpdword_t party_list) { - // Sonic & All-Stars Racing Transformed want specificly this code - // to skip loading party data. + // 5345085D wants specifically this code to skip loading party data. // This code is not documented in NT_STATUS code list return 0x807D0003; } diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index aed0c765b..612cc326c 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -716,7 +716,7 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id, i, // dummy image id 0, {0, 0}, - 8}; // flags=8 makes dummy achievements show up in Crackdown's + 8}; // flags=8 makes dummy achievements show up in 4D5307DC // achievements list. e->AppendItem(item); } diff --git a/src/xenia/kernel/xbdm/xbdm_misc.cc b/src/xenia/kernel/xbdm/xbdm_misc.cc index c352b2434..e15af9eb6 100644 --- a/src/xenia/kernel/xbdm/xbdm_misc.cc +++ b/src/xenia/kernel/xbdm/xbdm_misc.cc @@ -34,7 +34,7 @@ DECLARE_XBDM_EXPORT1(DmCloseLoadedModules, kDebug, kStub); MAKE_DUMMY_STUB_STATUS(DmFreePool); dword_result_t DmGetXbeInfo() { - // TODO(gibbed): Crackdown appears to expect this as success? + // TODO(gibbed): 4D5307DC appears to expect this as success? // Unknown arguments -- let's hope things don't explode. return 0x02DA0000; } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_audio_xma.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_audio_xma.cc index 951732494..f4c2c156e 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_audio_xma.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_audio_xma.cc @@ -119,7 +119,7 @@ static_assert_size(XMA_CONTEXT_INIT, 56); dword_result_t XMAInitializeContext(lpvoid_t context_ptr, pointer_t context_init) { - // Input buffers may be null (buffer 1 in Tony Hawk's American Wasteland). + // Input buffers may be null (buffer 1 in 415607D4). // Convert to host endianness. uint32_t input_buffer_0_guest_ptr = context_init->input_buffer_0_ptr; uint32_t input_buffer_0_physical_address = 0; diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc index a3e92c55f..5dd7b3309 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_debug.cc @@ -47,8 +47,8 @@ void HandleSetThreadName(pointer_t record) { return; } - // Shadowrun (and its demo) has a bug where it ends up passing freed memory - // for the name, so at the point of SetThreadName it's filled with junk. + // 4D5307D6 (and its demo) has a bug where it ends up passing freed memory for + // the name, so at the point of SetThreadName it's filled with junk. // TODO(gibbed): cvar for thread name encoding for conversion, some games use // SJIS and there's no way to automatically know this. diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc index f7dee19b9..37dc42aec 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc @@ -49,10 +49,10 @@ static bool IsValidPath(const std::string_view s, bool is_pattern) { if (got_asterisk) { // * must be followed by a . (*.) // - // Viva Piñata: Party Animals (4D530819) has a bug in its game code where - // it attempts to FindFirstFile() with filters of "Game:\\*_X3.rkv", - // "Game:\\m*_X3.rkv", and "Game:\\w*_X3.rkv" and will infinite loop if - // the path filter is allowed. + // 4D530819 has a bug in its game code where it attempts to + // FindFirstFile() with filters of "Game:\\*_X3.rkv", "Game:\\m*_X3.rkv", + // and "Game:\\w*_X3.rkv" and will infinite loop if the path filter is + // allowed. if (c != '.') { return false; } diff --git a/src/xenia/kernel/xobject.h b/src/xenia/kernel/xobject.h index 11d8ecb3c..67518b35c 100644 --- a/src/xenia/kernel/xobject.h +++ b/src/xenia/kernel/xobject.h @@ -111,7 +111,7 @@ struct X_OBJECT_TYPE { class XObject { public: - // Burnout Paradise needs proper handle value for certain calculations + // 45410806 needs proper handle value for certain calculations // It gets handle value from TLS (without base handle value is 0x88) // and substract 0xF8000088. Without base we're receiving wrong address // Instead of receiving address that starts with 0x82... we're receiving diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index 5b631e416..c7494e9fa 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -1531,12 +1531,11 @@ bool PhysicalHeap::Release(uint32_t base_address, uint32_t* out_region_size) { // Must invalidate here because the range being released may be reused in // another mapping of physical memory - but callback flags are set in each // heap separately (https://github.com/xenia-project/xenia/issues/1559 - - // dynamic vertices in Viva Pinata start screen and menu allocated in - // 0xA0000000 at addresses that overlap intro video textures in 0xE0000000, - // with the state of the allocator as of February 24th, 2020). If memory is - // invalidated in Alloc instead, Alloc won't be aware of callbacks enabled in - // other heaps, thus callback handlers will keep considering this range valid - // forever. + // dynamic vertices in 4D5307F2 start screen and menu allocated in 0xA0000000 + // at addresses that overlap intro video textures in 0xE0000000, with the + // state of the allocator as of February 24th, 2020). If memory is invalidated + // in Alloc instead, Alloc won't be aware of callbacks enabled in other heaps, + // thus callback handlers will keep considering this range valid forever. uint32_t region_size; if (QuerySize(base_address, ®ion_size)) { TriggerCallbacks(std::move(global_lock), base_address, region_size, true, From ddb08c60c2dc478a8d9305e95b822ad38ea03a70 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 5 Sep 2021 21:34:38 +0300 Subject: [PATCH 32/36] [Docs] Source traceability and game title ban --- .github/CONTRIBUTING.md | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e20dd4414..1404a5244 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -34,6 +34,60 @@ up later, so be sure to check it out. Basically: run `xb format` before you add a commit and you won't have a problem. +## Referencing Sources + +In code interacting with guest interfaces or protocols, where applicable, please +leave comments describing how the information included in it was obtained. For +code based on analysis of the response of the original Xbox 360 hardware or +software, please provide reproduction information, such as an outline of the +algorithm executed, arguments and results of function calls or processor +instructions involved, GPU or XMA commands and state register changes. Having +traceable sources helps solve multiple issues: + +* The legality of the submitted code can be verified easily. +* Additional analysis based on reproduction steps from prior research can be + done to discover more details or to research the behavior of other related + features. +* The accuracy and completeness of the information can be evaluated. Knowing + whether something is ground truth about the console's behavior or an + approximation (for example, based on similar functionality in Windows, the + Qualcomm Adreno 200 GPU, AMD's desktop GPU architectures; the Direct3D 11.3 + functional specification, which may be used as a generic fallback when no + information specific to the Xenos or Direct3D 9 is available) may help avoid + redoing work that has already been done if the findings are accurate, or + making potentially wrong conclusions about related functionality if there's no + certainty about the correctness of the information. In addition, it's useful + to describe how complete the implementation of a feature is — such as edge + cases checked and covered. If you are unsure if your code accurately reflects + the behavior of the console, or you have deliberately made deviations due to + the lack of prerequisites for an accurate implementation or for performance + reasons (in case of the latter, it's recommended to provide both options, + selectable with a configuration variable), or you just want more research to + be done in the future, please leave a TODO comment in the format provided in + [style_guide.md](../docs/style_guide.md). + +If you have verified your code by checking the correctness of the behavior of a +game, **do not** refer to it by its title trademark. To avoid unnecessary +dependencies on third parties, instead, use the hexadecimal title ID number +displayed in the title bar beside the name of the game. + +Do not leave any hard-coded references to specific games, even in title ID form, +in any part of the user interface, including the configuration file. If you want +to provide an example of a game where changing a configuration variable may have +a noticeable effect, use a code comment near the declaration of the variable +rather than its description string. Any game identifiers referenced in the user +interface must be obtained only from information provided by the user such as +game data files. + +Also, do not put any conditionals based on hard-coded identifiers of games — the +task of the project is researching the Xbox 360 console itself and documenting +its behavior by creating open implementations of its interfaces. Game-specific +hacks provide no help in achieving that, instead only complicating research by +introducing incorrect state and hiding the symptoms of actual issues. While +temporary workarounds, though discouraged, may be added in cases when progress +would be blocked otherwise in other areas, they must be expressed and reasoned +in terms of the common interface rather than logic internal to a specific game. + ## Clean Git History Tools such as `git bisect` are used on the repository regularly to check for and From 56b4a45002157cfe4319f20b54dc09d40aae8b4e Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 5 Sep 2021 21:43:08 +0300 Subject: [PATCH 33/36] [Docs] Avoiding in-game proper names --- .github/CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1404a5244..ad5f7f3cf 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -69,7 +69,10 @@ traceable sources helps solve multiple issues: If you have verified your code by checking the correctness of the behavior of a game, **do not** refer to it by its title trademark. To avoid unnecessary dependencies on third parties, instead, use the hexadecimal title ID number -displayed in the title bar beside the name of the game. +displayed in the title bar beside the name of the game. It's also recommended to +avoid using proper names of game content if they can be replaced with easily +understandable pointers not including them, such as "first mission", +"protagonist", "enemy aircraft". Do not leave any hard-coded references to specific games, even in title ID form, in any part of the user interface, including the configuration file. If you want From 44847abb98bf39b982bda2aa185f9f65ff3b1281 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Tue, 7 Sep 2021 21:11:56 +0300 Subject: [PATCH 34/36] [Kernel] Remove a TODO for a verified reference --- src/xenia/kernel/xam/xam_net.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/xenia/kernel/xam/xam_net.cc b/src/xenia/kernel/xam/xam_net.cc index 413639156..e9e8e8759 100644 --- a/src/xenia/kernel/xam/xam_net.cc +++ b/src/xenia/kernel/xam/xam_net.cc @@ -251,8 +251,6 @@ dword_result_t NetDll_WSAStartup(dword_t caller, word_t version, // Some games (5841099F) want this value round-tripped - they'll compare if // it changes and bugcheck if it does. - // TODO(Triang3l): Verify if the title ID in the comment is correct - added - // by benvanik as an acronym initially. uint32_t vendor_ptr = xe::load_and_swap(data_out + 0x190); xe::store_and_swap(data_out + 0x190, vendor_ptr); } From 9d992e3d0693d32d6058a73b0dec23e8039fd2a2 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 11 Sep 2021 23:31:52 +0300 Subject: [PATCH 35/36] [Kernel] Rename sin_zero due to #define on Android --- src/xenia/kernel/xam/xam_net.cc | 2 +- src/xenia/kernel/xsocket.cc | 4 +++- src/xenia/kernel/xsocket.h | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/xenia/kernel/xam/xam_net.cc b/src/xenia/kernel/xam/xam_net.cc index e9e8e8759..4c5c0ead4 100644 --- a/src/xenia/kernel/xam/xam_net.cc +++ b/src/xenia/kernel/xam/xam_net.cc @@ -950,7 +950,7 @@ dword_result_t NetDll_recvfrom(dword_t caller, dword_t socket_handle, from_ptr->sin_family = native_from.sin_family; from_ptr->sin_port = native_from.sin_port; from_ptr->sin_addr = native_from.sin_addr; - memset(from_ptr->sin_zero, 0, 8); + std::memset(from_ptr->x_sin_zero, 0, sizeof(from_ptr->x_sin_zero)); } if (fromlen_ptr) { *fromlen_ptr = native_fromlen; diff --git a/src/xenia/kernel/xsocket.cc b/src/xenia/kernel/xsocket.cc index ea15734e7..8bbc0375e 100644 --- a/src/xenia/kernel/xsocket.cc +++ b/src/xenia/kernel/xsocket.cc @@ -9,6 +9,8 @@ #include "src/xenia/kernel/xsocket.h" +#include + #include "xenia/base/platform.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/xam/xam_module.h" @@ -200,7 +202,7 @@ int XSocket::RecvFrom(uint8_t* buf, uint32_t buf_len, uint32_t flags, from->sin_family = nfrom.sin_family; from->sin_addr = ntohl(nfrom.sin_addr.s_addr); // BE <- BE from->sin_port = nfrom.sin_port; - memset(&from->sin_zero, 0, 8); + std::memset(from->x_sin_zero, 0, sizeof(from->x_sin_zero)); } if (from_len) { diff --git a/src/xenia/kernel/xsocket.h b/src/xenia/kernel/xsocket.h index 8c5103ec9..f77e2d8eb 100644 --- a/src/xenia/kernel/xsocket.h +++ b/src/xenia/kernel/xsocket.h @@ -10,6 +10,7 @@ #ifndef XENIA_KERNEL_XSOCKET_H_ #define XENIA_KERNEL_XSOCKET_H_ +#include #include #include "xenia/base/byte_order.h" @@ -42,7 +43,8 @@ struct XSOCKADDR_IN { // Always big-endian! xe::be sin_port; xe::be sin_addr; - char sin_zero[8]; + // sin_zero is defined as __pad on Android, so prefixed here. + char x_sin_zero[8]; }; // Xenia native sockaddr_in @@ -53,7 +55,7 @@ struct N_XSOCKADDR_IN { sin_family = other.sin_family; sin_port = other.sin_port; sin_addr = other.sin_addr; - std::memset(sin_zero, 0, 8); + std::memset(x_sin_zero, 0, sizeof(x_sin_zero)); return *this; } @@ -61,7 +63,8 @@ struct N_XSOCKADDR_IN { uint16_t sin_family; xe::be sin_port; xe::be sin_addr; - char sin_zero[8]; + // sin_zero is defined as __pad on Android, so prefixed here. + char x_sin_zero[8]; }; class XSocket : public XObject { From 6241b4f907ef09dd07411f84515adc9b9aee1c99 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 12 Sep 2021 13:04:03 +0300 Subject: [PATCH 36/36] [Kernel] stringstream<< > string.push_back as LLVM libc++ doesn't support char16_t stream --- src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc index f350fd9e5..66a2141d7 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include "xenia/base/logging.h" #include "xenia/kernel/kernel_state.h" @@ -737,15 +738,15 @@ class StringFormatData : public FormatData { if (c >= 0x100) { return false; } - output_ << (char)c; + output_.push_back(char(c)); return true; } - std::string str() const { return output_.str(); } + const std::string& str() const { return output_; } private: const uint8_t* input_; - std::ostringstream output_; + std::string output_; }; class WideStringFormatData : public FormatData { @@ -771,15 +772,15 @@ class WideStringFormatData : public FormatData { } bool put(uint16_t c) { - output_ << (char16_t)c; + output_.push_back(char16_t(c)); return true; } - std::u16string wstr() const { return output_.str(); } + const std::u16string& wstr() const { return output_; } private: const uint16_t* input_; - std::basic_stringstream output_; + std::u16string output_; }; class WideCountFormatData : public FormatData {