diff --git a/common/video/vulkan/vulkan_context.cpp b/common/video/vulkan/vulkan_context.cpp index 2e80fb3e..7c9b2f95 100644 --- a/common/video/vulkan/vulkan_context.cpp +++ b/common/video/vulkan/vulkan_context.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "vulkan_context.hpp" namespace Vulkan @@ -93,45 +94,61 @@ std::vector Vulkan::Context::get_device_list() } #ifdef VK_USE_PLATFORM_WIN32_KHR -bool Context::init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device) +bool Context::init_win32() { instance = create_instance_preamble(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); if (!instance) return false; + return init(preferred_device); +} + +bool Context::create_win32_surface(HINSTANCE hinstance, HWND hwnd) +{ auto win32_surface_create_info = vk::Win32SurfaceCreateInfoKHR{} .setHinstance(hinstance) .setHwnd(hwnd); - surface = instance->createWin32SurfaceKHRUnique(win32_surface_create_info).value; - if (!surface) + auto retval = instance->createWin32SurfaceKHRUnique(win32_surface_create_info); + if (retval.result != vk::Result::eSuccess) return false; - return init(preferred_device); + surface = std::move(retval.value); + return true; } #endif #ifdef VK_USE_PLATFORM_XLIB_KHR -bool Context::init_Xlib(Display *dpy, Window xid, int preferred_device) +bool Context::init_Xlib() { instance = create_instance_preamble(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); if (!instance) return false; + return init(); +} + +bool Context::create_Xlib_surface(Display *dpy, Window xid) +{ auto retval = instance->createXlibSurfaceKHRUnique({ {}, dpy, xid }); if (retval.result != vk::Result::eSuccess) return false; surface = std::move(retval.value); - return init(preferred_device); + return true; } #endif #ifdef VK_USE_PLATFORM_WAYLAND_KHR -bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_width, int initial_height, int preferred_device) +bool Context::init_wayland() { instance = create_instance_preamble(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); if (!instance) return false; + return init(); +} + +bool Context::create_wayland_surface(wl_display *dpy, wl_surface *parent) +{ auto wayland_surface_create_info = vk::WaylandSurfaceCreateInfoKHR{} .setSurface(parent) .setDisplay(dpy); @@ -141,18 +158,29 @@ bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_widt return false; surface = std::move(new_surface); - return init(preferred_device, initial_width, initial_height); + return true; } #endif -bool Context::init(int preferred_device, int initial_width, int initial_height) +bool Context::destroy_surface() { - init_device(preferred_device); + wait_idle(); + if (swapchain) + swapchain->uncreate(); + + surface.reset(); + + return true; +} + +bool Context::init() +{ + init_device(); init_vma(); init_command_pool(); init_descriptor_pool(); - create_swapchain(initial_width, initial_height); + swapchain = std::make_unique(*this); wait_idle(); return true; } @@ -192,7 +220,7 @@ static bool find_extension(std::vector &props, const ch }) != props.end(); }; -static uint32_t find_graphics_queue(vk::PhysicalDevice &device) +static std::optional find_graphics_queue(vk::PhysicalDevice &device) { auto queue_props = device.getQueueFamilyProperties(); for (size_t i = 0; i < queue_props.size(); i++) @@ -203,7 +231,7 @@ static uint32_t find_graphics_queue(vk::PhysicalDevice &device) } } - return UINT32_MAX; + return std::nullopt; } static bool check_extensions(std::vector &required_extensions, vk::PhysicalDevice &device) @@ -217,7 +245,7 @@ static bool check_extensions(std::vector &required_extensions, vk: return true; }; -bool Context::init_device(int preferred_device) +bool Context::init_device() { std::vector required_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, @@ -251,8 +279,9 @@ bool Context::init_device(int preferred_device) if (!device_chosen) return false; - graphics_queue_family_index = find_graphics_queue(physical_device); - if (graphics_queue_family_index == UINT32_MAX) + if (auto index = find_graphics_queue(physical_device)) + graphics_queue_family_index = *index; + else return false; std::vector priorities = { 1.0f }; @@ -262,14 +291,6 @@ bool Context::init_device(int preferred_device) device = physical_device.createDevice(dci).value; queue = device.getQueue(graphics_queue_family_index, 0); - auto surface_formats = physical_device.getSurfaceFormatsKHR(surface.get()).value; - if (std::find_if(surface_formats.begin(), - surface_formats.end(), - [](vk::SurfaceFormatKHR &f) { - return (f.format == vk::Format::eB8G8R8A8Unorm); - }) == surface_formats.end()) - return false; - return true; } @@ -289,16 +310,15 @@ bool Context::init_vma() return true; } -bool Context::create_swapchain(int width, int height) +bool Context::create_swapchain() { wait_idle(); - swapchain = std::make_unique(*this); - return swapchain->create(2, width, height); + return swapchain->create(); } -bool Context::recreate_swapchain(int width, int height) +bool Context::recreate_swapchain() { - return swapchain->recreate(width, height); + return swapchain->recreate(); } void Context::wait_idle() diff --git a/common/video/vulkan/vulkan_context.hpp b/common/video/vulkan/vulkan_context.hpp index c15df4f8..0e05c7d7 100644 --- a/common/video/vulkan/vulkan_context.hpp +++ b/common/video/vulkan/vulkan_context.hpp @@ -22,22 +22,28 @@ class Context Context(); ~Context(); #ifdef VK_USE_PLATFORM_XLIB_KHR - bool init_Xlib(Display *dpy, Window xid, int preferred_device = -1); + bool init_Xlib(); + bool create_Xlib_surface(Display *dpy, Window xid); #endif #ifdef VK_USE_PLATFORM_WAYLAND_KHR - bool init_wayland(wl_display *dpy, wl_surface *parent, int width, int height, int preferred_device = -1); + bool init_wayland(); + bool create_wayland_surface(wl_display *dpy, wl_surface *parent); #endif #ifdef VK_USE_PLATFORM_WIN32_KHR - bool init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device = -1); + bool init_win32(); + bool create_win32_surface(HINSTANCE hinstance, HWND hwnd); #endif - bool init(int preferred_device = -1, int initial_width = -1, int initial_height = -1); - bool create_swapchain(int width = -1, int height = -1); - bool recreate_swapchain(int width = -1, int height = -1); + bool init(); + bool create_swapchain(); + bool recreate_swapchain(); + bool destroy_surface(); void wait_idle(); vk::CommandBuffer begin_cmd_buffer(); void end_cmd_buffer(); void hard_barrier(vk::CommandBuffer cmd); static std::vector get_device_list(); + void set_preferred_device(int device) { preferred_device = device; }; + void unset_preferred_device() { preferred_device = -1; }; vma::Allocator allocator; vk::Device device; @@ -53,9 +59,10 @@ class Context private: bool init_vma(); - bool init_device(int preferred_device = 0); + bool init_device(); bool init_command_pool(); bool init_descriptor_pool(); + int preferred_device; #ifdef VK_USE_PLATFORM_XLIB_KHR Display *xlib_display; diff --git a/common/video/vulkan/vulkan_swapchain.cpp b/common/video/vulkan/vulkan_swapchain.cpp index d188ee2b..403a64d8 100644 --- a/common/video/vulkan/vulkan_swapchain.cpp +++ b/common/video/vulkan/vulkan_swapchain.cpp @@ -9,7 +9,6 @@ Swapchain::Swapchain(Context &context_) { device = context.device; queue = context.queue; - surface = context.surface.get(); physical_device = context.physical_device; command_pool = context.command_pool.get(); create_render_pass(); @@ -77,17 +76,17 @@ void Swapchain::create_render_pass() .setDependencies(subpass_dependency) .setAttachments(attachment_description); - render_pass = device.createRenderPassUnique(render_pass_create_info).value; + render_pass = context.device.createRenderPassUnique(render_pass_create_info).value; } -bool Swapchain::recreate(int new_width, int new_height) +bool Swapchain::recreate() { if (swapchain_object) { device.waitIdle(); } - return create(num_swapchain_images, new_width, new_height); + return create(); } vk::Image Swapchain::get_image() @@ -123,7 +122,7 @@ bool Swapchain::check_and_resize(int width, int height) if (width == -1 && height == -1) { - surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface).value; + surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(context.surface.get()).value; width = surface_capabilities.currentExtent.width; height = surface_capabilities.currentExtent.height; } @@ -133,24 +132,34 @@ bool Swapchain::check_and_resize(int width, int height) if (extents.width != (uint32_t)width || extents.height != (uint32_t)height) { - recreate(width, height); + set_desired_size(width, height); + recreate(); return true; } return false; } -bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, int new_height) +bool Swapchain::uncreate() +{ + frames.clear(); + image_data.clear(); + swapchain_object.reset(); + + return true; +} + +bool Swapchain::create() { frames.clear(); image_data.clear(); - auto surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface).value; + auto surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(context.surface.get()).value; - if (surface_capabilities.minImageCount > desired_num_swapchain_images) + if (desired_latency == - 1 || (int)surface_capabilities.minImageCount > desired_latency) num_swapchain_images = surface_capabilities.minImageCount; else - num_swapchain_images = desired_num_swapchain_images; + num_swapchain_images = desired_latency; // If extents aren't reported (Wayland), we have to rely on Wayland to report // the size, so keep current extent. @@ -168,11 +177,11 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, } } - if (new_width > 0 && new_height > 0) + if (desired_width > 0 && desired_height > 0) { // No buffer is allocated for surface yet - extents.width = new_width; - extents.height = new_height; + extents.width = desired_width; + extents.height = desired_height; } else if (extents.width < 1 || extents.height < 1) { @@ -191,14 +200,6 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, if (extents.height < surface_capabilities.minImageExtent.height) extents.height = surface_capabilities.minImageExtent.height; - auto present_modes = physical_device.getSurfacePresentModesKHR(surface).value; - supports_mailbox = vector_find(present_modes, vk::PresentModeKHR::eMailbox); - supports_immediate = vector_find(present_modes, vk::PresentModeKHR::eImmediate); - supports_relaxed = vector_find(present_modes, vk::PresentModeKHR::eFifoRelaxed); - - auto swapchain_maintenance_info = vk::SwapchainPresentModesCreateInfoEXT{} - .setPresentModes(present_modes); - auto swapchain_create_info = vk::SwapchainCreateInfoKHR{} .setMinImageCount(num_swapchain_images) .setImageFormat(vk::Format::eB8G8R8A8Unorm) @@ -209,11 +210,10 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, .setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque) .setClipped(true) .setPresentMode(get_present_mode()) - .setSurface(surface) + .setSurface(context.surface.get()) .setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity) .setImageArrayLayers(1) - .setQueueFamilyIndices(graphics_queue_index) - .setPNext(&swapchain_maintenance_info); + .setQueueFamilyIndices(graphics_queue_index); swapchain_object.reset(); auto resval = device.createSwapchainKHRUnique(swapchain_create_info); @@ -299,7 +299,7 @@ bool Swapchain::begin_frame() } vk::ResultValue result_value(vk::Result::eSuccess, 0); - result_value = device.acquireNextImageKHR(swapchain_object.get(), UINT64_MAX, frame.acquire.get()); + result_value = device.acquireNextImageKHR(swapchain_object.get(), 33333333, frame.acquire.get()); if (result_value.result == vk::Result::eErrorOutOfDateKHR || result_value.result == vk::Result::eSuboptimalKHR) diff --git a/common/video/vulkan/vulkan_swapchain.hpp b/common/video/vulkan/vulkan_swapchain.hpp index 78c7ad8f..5a624419 100644 --- a/common/video/vulkan/vulkan_swapchain.hpp +++ b/common/video/vulkan/vulkan_swapchain.hpp @@ -13,10 +13,16 @@ class Swapchain public: Swapchain(Context &); ~Swapchain(); - bool create(unsigned int num_frames, int width = -1, int height = -1); - bool recreate(int width = -1, int height = -1); + bool create(); + bool uncreate(); + bool recreate(); bool create_resources(); bool check_and_resize(int width = -1, int height = -1); + Swapchain &set_desired_size(int width, int height) { desired_width = width; desired_height = height; return *this; } + void unset_desired_size() { desired_width = -1; desired_height = -1; } + Swapchain &set_desired_latency(int latency) { desired_latency = latency; return *this; } + void unset_desired_latency() { desired_latency = -1; } + bool begin_frame(); void begin_render_pass(); void end_render_pass(); @@ -61,6 +67,9 @@ class Swapchain unsigned int current_frame = 0; unsigned int current_swapchain_image = 0; unsigned int num_swapchain_images = 0; + int desired_width = -1; + int desired_height = -1; + int desired_latency = -1; uint64_t presentation_id = 0; bool vsync = true; bool supports_immediate = false; @@ -71,7 +80,6 @@ class Swapchain Vulkan::Context &context; vk::Device device; - vk::SurfaceKHR surface; vk::CommandPool command_pool; vk::PhysicalDevice physical_device; vk::Queue queue; diff --git a/gtk/src/gtk_display_driver_vulkan.cpp b/gtk/src/gtk_display_driver_vulkan.cpp index 54d737e8..3937a3ef 100644 --- a/gtk/src/gtk_display_driver_vulkan.cpp +++ b/gtk/src/gtk_display_driver_vulkan.cpp @@ -91,7 +91,10 @@ void S9xVulkanDisplayDriver::refresh() #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_WINDOW(drawing_area->get_window()->gobj())) + { std::tie(new_width, new_height) = wayland_surface->get_size_for_metrics(get_metrics(*drawing_area)); + context->swapchain->set_desired_size(new_width, new_height); + } else #endif { @@ -101,7 +104,7 @@ void S9xVulkanDisplayDriver::refresh() if (new_width != current_width || new_height != current_height) { - context->recreate_swapchain(new_width, new_height); + context->recreate_swapchain(); context->wait_idle(); current_width = new_width; current_height = new_height; @@ -128,10 +131,14 @@ int S9xVulkanDisplayDriver::init() wl_display *display = gdk_wayland_display_get_wl_display(drawing_area->get_display()->gobj()); if (!wayland_surface->attach(display, surface, get_metrics(*drawing_area))) - return -1; - - if (!context->init_wayland(wayland_surface->display, wayland_surface->child, current_width, current_height)) - return -1; + goto abort; + if (!context->init_wayland()) + goto abort; + if (!context->create_wayland_surface(wayland_surface->display, wayland_surface->child)) + goto abort; + context->swapchain->set_desired_size(current_width, current_height); + if (!context->create_swapchain()) + goto abort; } #endif if (GDK_IS_X11_WINDOW(drawing_area->get_window()->gobj())) @@ -139,8 +146,12 @@ int S9xVulkanDisplayDriver::init() display = gdk_x11_display_get_xdisplay(drawing_area->get_display()->gobj()); xid = gdk_x11_window_get_xid(drawing_area->get_window()->gobj()); - if (!context->init_Xlib(display, xid)) - return -1; + if (!context->init_Xlib()) + goto abort; + if (!context->create_Xlib_surface(display, xid)) + goto abort; + if (!context->create_swapchain()) + goto abort; } device = context->device; @@ -168,6 +179,10 @@ int S9xVulkanDisplayDriver::init() simple_output = std::make_unique(context.get(), vk::Format::eR5G6B5UnormPack16); return 0; + +abort: + context.reset(); + return -1; } void S9xVulkanDisplayDriver::deinit() diff --git a/qt/src/EmuCanvasVulkan.cpp b/qt/src/EmuCanvasVulkan.cpp index 864b738a..20bb6903 100644 --- a/qt/src/EmuCanvasVulkan.cpp +++ b/qt/src/EmuCanvasVulkan.cpp @@ -28,7 +28,7 @@ EmuCanvasVulkan::EmuCanvasVulkan(EmuConfig *config, QWidget *parent, QWidget *ma EmuCanvasVulkan::~EmuCanvasVulkan() { - deinit(); + assert(!context); } bool EmuCanvasVulkan::initImGui() @@ -85,14 +85,16 @@ bool EmuCanvasVulkan::createContext() QGuiApplication::sync(); context = std::make_unique(); + context->set_preferred_device(config->display_device_index); #ifdef _WIN32 auto hwnd = (HWND)winId(); if (!context->init_win32(nullptr, hwnd, config->display_device_index)) - { - context.reset(); - return false; - } + goto fail; + if (!context->create_win32_surface(nullptr, hwnd)) + goto fail; + if (!context->swapchain->create()) + goto fail; #else if (platform == "wayland") { @@ -101,21 +103,31 @@ bool EmuCanvasVulkan::createContext() auto surface = (wl_surface *)pni->nativeResourceForWindow("surface", main_window->windowHandle()); wayland_surface->attach(display, surface, { parent->x() - main_window->x(), parent->y() - main_window->y(), width(), height(), static_cast(devicePixelRatio()) }); auto [scaled_width, scaled_height] = wayland_surface->get_size(); - if (!context->init_wayland(display, wayland_surface->child, scaled_width, scaled_height, config->display_device_index)) - { - context.reset(); - return false; - } + + if (!context->init_wayland()) + goto fail; + + if (!context->create_wayland_surface(display, wayland_surface->child)) + goto fail; + + context->swapchain->set_desired_size(scaled_width, scaled_height); + if (!context->swapchain->create()) + goto fail; } else if (platform == "xcb") { auto display = (Display *)pni->nativeResourceForWindow("display", window); auto xid = (Window)winId(); - if (!context->init_Xlib(display, xid, config->display_device_index)) - { - context.reset(); - return false; - } + + if (!context->init_Xlib()) + goto fail; + + if (!context->create_Xlib_surface(display, xid)) + goto fail; + + context->wait_idle(); + if (!context->swapchain->create()) + goto fail; } #endif @@ -130,6 +142,10 @@ bool EmuCanvasVulkan::createContext() paintEvent(nullptr); return true; + +fail: + context.reset(); + return false; } void EmuCanvasVulkan::tryLoadShader()