Vulkan: Separate context, surface, and swapchain.

This commit is contained in:
BearOso 2024-10-03 11:18:23 -05:00
parent 72e4946410
commit febcf27482
6 changed files with 152 additions and 86 deletions

View File

@ -1,6 +1,7 @@
#include <cstring>
#include <vector>
#include <string>
#include <optional>
#include "vulkan_context.hpp"
namespace Vulkan
@ -93,45 +94,61 @@ std::vector<std::string> 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<Swapchain>(*this);
wait_idle();
return true;
}
@ -192,7 +220,7 @@ static bool find_extension(std::vector<vk::ExtensionProperties> &props, const ch
}) != props.end();
};
static uint32_t find_graphics_queue(vk::PhysicalDevice &device)
static std::optional<uint32_t> 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<const char *> &required_extensions, vk::PhysicalDevice &device)
@ -217,7 +245,7 @@ static bool check_extensions(std::vector<const char *> &required_extensions, vk:
return true;
};
bool Context::init_device(int preferred_device)
bool Context::init_device()
{
std::vector<const char *> 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<float> 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<Swapchain>(*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()

View File

@ -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<std::string> 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;

View File

@ -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<uint32_t> 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)

View File

@ -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;

View File

@ -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<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16);
return 0;
abort:
context.reset();
return -1;
}
void S9xVulkanDisplayDriver::deinit()

View File

@ -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<Vulkan::Context>();
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<int>(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()