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 <cstring>
#include <vector> #include <vector>
#include <string> #include <string>
#include <optional>
#include "vulkan_context.hpp" #include "vulkan_context.hpp"
namespace Vulkan namespace Vulkan
@ -93,45 +94,61 @@ std::vector<std::string> Vulkan::Context::get_device_list()
} }
#ifdef VK_USE_PLATFORM_WIN32_KHR #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); instance = create_instance_preamble(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
if (!instance) if (!instance)
return false; return false;
return init(preferred_device);
}
bool Context::create_win32_surface(HINSTANCE hinstance, HWND hwnd)
{
auto win32_surface_create_info = vk::Win32SurfaceCreateInfoKHR{} auto win32_surface_create_info = vk::Win32SurfaceCreateInfoKHR{}
.setHinstance(hinstance) .setHinstance(hinstance)
.setHwnd(hwnd); .setHwnd(hwnd);
surface = instance->createWin32SurfaceKHRUnique(win32_surface_create_info).value; auto retval = instance->createWin32SurfaceKHRUnique(win32_surface_create_info);
if (!surface) if (retval.result != vk::Result::eSuccess)
return false; return false;
return init(preferred_device); surface = std::move(retval.value);
return true;
} }
#endif #endif
#ifdef VK_USE_PLATFORM_XLIB_KHR #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); instance = create_instance_preamble(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
if (!instance) if (!instance)
return false; return false;
return init();
}
bool Context::create_Xlib_surface(Display *dpy, Window xid)
{
auto retval = instance->createXlibSurfaceKHRUnique({ {}, dpy, xid }); auto retval = instance->createXlibSurfaceKHRUnique({ {}, dpy, xid });
if (retval.result != vk::Result::eSuccess) if (retval.result != vk::Result::eSuccess)
return false; return false;
surface = std::move(retval.value); surface = std::move(retval.value);
return init(preferred_device); return true;
} }
#endif #endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR #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); instance = create_instance_preamble(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
if (!instance) if (!instance)
return false; return false;
return init();
}
bool Context::create_wayland_surface(wl_display *dpy, wl_surface *parent)
{
auto wayland_surface_create_info = vk::WaylandSurfaceCreateInfoKHR{} auto wayland_surface_create_info = vk::WaylandSurfaceCreateInfoKHR{}
.setSurface(parent) .setSurface(parent)
.setDisplay(dpy); .setDisplay(dpy);
@ -141,18 +158,29 @@ bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_widt
return false; return false;
surface = std::move(new_surface); surface = std::move(new_surface);
return init(preferred_device, initial_width, initial_height); return true;
} }
#endif #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_vma();
init_command_pool(); init_command_pool();
init_descriptor_pool(); init_descriptor_pool();
create_swapchain(initial_width, initial_height); swapchain = std::make_unique<Swapchain>(*this);
wait_idle(); wait_idle();
return true; return true;
} }
@ -192,7 +220,7 @@ static bool find_extension(std::vector<vk::ExtensionProperties> &props, const ch
}) != props.end(); }) != 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(); auto queue_props = device.getQueueFamilyProperties();
for (size_t i = 0; i < queue_props.size(); i++) 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) 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; return true;
}; };
bool Context::init_device(int preferred_device) bool Context::init_device()
{ {
std::vector<const char *> required_extensions = { std::vector<const char *> required_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_EXTENSION_NAME,
@ -251,8 +279,9 @@ bool Context::init_device(int preferred_device)
if (!device_chosen) if (!device_chosen)
return false; return false;
graphics_queue_family_index = find_graphics_queue(physical_device); if (auto index = find_graphics_queue(physical_device))
if (graphics_queue_family_index == UINT32_MAX) graphics_queue_family_index = *index;
else
return false; return false;
std::vector<float> priorities = { 1.0f }; std::vector<float> priorities = { 1.0f };
@ -262,14 +291,6 @@ bool Context::init_device(int preferred_device)
device = physical_device.createDevice(dci).value; device = physical_device.createDevice(dci).value;
queue = device.getQueue(graphics_queue_family_index, 0); 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; return true;
} }
@ -289,16 +310,15 @@ bool Context::init_vma()
return true; return true;
} }
bool Context::create_swapchain(int width, int height) bool Context::create_swapchain()
{ {
wait_idle(); wait_idle();
swapchain = std::make_unique<Swapchain>(*this); return swapchain->create();
return swapchain->create(2, width, height);
} }
bool Context::recreate_swapchain(int width, int height) bool Context::recreate_swapchain()
{ {
return swapchain->recreate(width, height); return swapchain->recreate();
} }
void Context::wait_idle() void Context::wait_idle()

View File

@ -22,22 +22,28 @@ class Context
Context(); Context();
~Context(); ~Context();
#ifdef VK_USE_PLATFORM_XLIB_KHR #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 #endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR #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 #endif
#ifdef VK_USE_PLATFORM_WIN32_KHR #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 #endif
bool init(int preferred_device = -1, int initial_width = -1, int initial_height = -1); bool init();
bool create_swapchain(int width = -1, int height = -1); bool create_swapchain();
bool recreate_swapchain(int width = -1, int height = -1); bool recreate_swapchain();
bool destroy_surface();
void wait_idle(); void wait_idle();
vk::CommandBuffer begin_cmd_buffer(); vk::CommandBuffer begin_cmd_buffer();
void end_cmd_buffer(); void end_cmd_buffer();
void hard_barrier(vk::CommandBuffer cmd); void hard_barrier(vk::CommandBuffer cmd);
static std::vector<std::string> get_device_list(); 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; vma::Allocator allocator;
vk::Device device; vk::Device device;
@ -53,9 +59,10 @@ class Context
private: private:
bool init_vma(); bool init_vma();
bool init_device(int preferred_device = 0); bool init_device();
bool init_command_pool(); bool init_command_pool();
bool init_descriptor_pool(); bool init_descriptor_pool();
int preferred_device;
#ifdef VK_USE_PLATFORM_XLIB_KHR #ifdef VK_USE_PLATFORM_XLIB_KHR
Display *xlib_display; Display *xlib_display;

View File

@ -9,7 +9,6 @@ Swapchain::Swapchain(Context &context_)
{ {
device = context.device; device = context.device;
queue = context.queue; queue = context.queue;
surface = context.surface.get();
physical_device = context.physical_device; physical_device = context.physical_device;
command_pool = context.command_pool.get(); command_pool = context.command_pool.get();
create_render_pass(); create_render_pass();
@ -77,17 +76,17 @@ void Swapchain::create_render_pass()
.setDependencies(subpass_dependency) .setDependencies(subpass_dependency)
.setAttachments(attachment_description); .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) if (swapchain_object)
{ {
device.waitIdle(); device.waitIdle();
} }
return create(num_swapchain_images, new_width, new_height); return create();
} }
vk::Image Swapchain::get_image() vk::Image Swapchain::get_image()
@ -123,7 +122,7 @@ bool Swapchain::check_and_resize(int width, int height)
if (width == -1 && height == -1) 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; width = surface_capabilities.currentExtent.width;
height = surface_capabilities.currentExtent.height; 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) if (extents.width != (uint32_t)width || extents.height != (uint32_t)height)
{ {
recreate(width, height); set_desired_size(width, height);
recreate();
return true; return true;
} }
return false; 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(); frames.clear();
image_data.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; num_swapchain_images = surface_capabilities.minImageCount;
else 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 // If extents aren't reported (Wayland), we have to rely on Wayland to report
// the size, so keep current extent. // 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 // No buffer is allocated for surface yet
extents.width = new_width; extents.width = desired_width;
extents.height = new_height; extents.height = desired_height;
} }
else if (extents.width < 1 || extents.height < 1) 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) if (extents.height < surface_capabilities.minImageExtent.height)
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{} auto swapchain_create_info = vk::SwapchainCreateInfoKHR{}
.setMinImageCount(num_swapchain_images) .setMinImageCount(num_swapchain_images)
.setImageFormat(vk::Format::eB8G8R8A8Unorm) .setImageFormat(vk::Format::eB8G8R8A8Unorm)
@ -209,11 +210,10 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque) .setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setClipped(true) .setClipped(true)
.setPresentMode(get_present_mode()) .setPresentMode(get_present_mode())
.setSurface(surface) .setSurface(context.surface.get())
.setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity) .setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity)
.setImageArrayLayers(1) .setImageArrayLayers(1)
.setQueueFamilyIndices(graphics_queue_index) .setQueueFamilyIndices(graphics_queue_index);
.setPNext(&swapchain_maintenance_info);
swapchain_object.reset(); swapchain_object.reset();
auto resval = device.createSwapchainKHRUnique(swapchain_create_info); 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); 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 || if (result_value.result == vk::Result::eErrorOutOfDateKHR ||
result_value.result == vk::Result::eSuboptimalKHR) result_value.result == vk::Result::eSuboptimalKHR)

View File

@ -13,10 +13,16 @@ class Swapchain
public: public:
Swapchain(Context &); Swapchain(Context &);
~Swapchain(); ~Swapchain();
bool create(unsigned int num_frames, int width = -1, int height = -1); bool create();
bool recreate(int width = -1, int height = -1); bool uncreate();
bool recreate();
bool create_resources(); bool create_resources();
bool check_and_resize(int width = -1, int height = -1); 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(); bool begin_frame();
void begin_render_pass(); void begin_render_pass();
void end_render_pass(); void end_render_pass();
@ -61,6 +67,9 @@ class Swapchain
unsigned int current_frame = 0; unsigned int current_frame = 0;
unsigned int current_swapchain_image = 0; unsigned int current_swapchain_image = 0;
unsigned int num_swapchain_images = 0; unsigned int num_swapchain_images = 0;
int desired_width = -1;
int desired_height = -1;
int desired_latency = -1;
uint64_t presentation_id = 0; uint64_t presentation_id = 0;
bool vsync = true; bool vsync = true;
bool supports_immediate = false; bool supports_immediate = false;
@ -71,7 +80,6 @@ class Swapchain
Vulkan::Context &context; Vulkan::Context &context;
vk::Device device; vk::Device device;
vk::SurfaceKHR surface;
vk::CommandPool command_pool; vk::CommandPool command_pool;
vk::PhysicalDevice physical_device; vk::PhysicalDevice physical_device;
vk::Queue queue; vk::Queue queue;

View File

@ -91,7 +91,10 @@ void S9xVulkanDisplayDriver::refresh()
#ifdef GDK_WINDOWING_WAYLAND #ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_WINDOW(drawing_area->get_window()->gobj())) 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)); 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 else
#endif #endif
{ {
@ -101,7 +104,7 @@ void S9xVulkanDisplayDriver::refresh()
if (new_width != current_width || new_height != current_height) if (new_width != current_width || new_height != current_height)
{ {
context->recreate_swapchain(new_width, new_height); context->recreate_swapchain();
context->wait_idle(); context->wait_idle();
current_width = new_width; current_width = new_width;
current_height = new_height; 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()); wl_display *display = gdk_wayland_display_get_wl_display(drawing_area->get_display()->gobj());
if (!wayland_surface->attach(display, surface, get_metrics(*drawing_area))) if (!wayland_surface->attach(display, surface, get_metrics(*drawing_area)))
return -1; goto abort;
if (!context->init_wayland())
if (!context->init_wayland(wayland_surface->display, wayland_surface->child, current_width, current_height)) goto abort;
return -1; 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 #endif
if (GDK_IS_X11_WINDOW(drawing_area->get_window()->gobj())) 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()); display = gdk_x11_display_get_xdisplay(drawing_area->get_display()->gobj());
xid = gdk_x11_window_get_xid(drawing_area->get_window()->gobj()); xid = gdk_x11_window_get_xid(drawing_area->get_window()->gobj());
if (!context->init_Xlib(display, xid)) if (!context->init_Xlib())
return -1; goto abort;
if (!context->create_Xlib_surface(display, xid))
goto abort;
if (!context->create_swapchain())
goto abort;
} }
device = context->device; device = context->device;
@ -168,6 +179,10 @@ int S9xVulkanDisplayDriver::init()
simple_output = std::make_unique<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16); simple_output = std::make_unique<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16);
return 0; return 0;
abort:
context.reset();
return -1;
} }
void S9xVulkanDisplayDriver::deinit() void S9xVulkanDisplayDriver::deinit()

View File

@ -28,7 +28,7 @@ EmuCanvasVulkan::EmuCanvasVulkan(EmuConfig *config, QWidget *parent, QWidget *ma
EmuCanvasVulkan::~EmuCanvasVulkan() EmuCanvasVulkan::~EmuCanvasVulkan()
{ {
deinit(); assert(!context);
} }
bool EmuCanvasVulkan::initImGui() bool EmuCanvasVulkan::initImGui()
@ -85,14 +85,16 @@ bool EmuCanvasVulkan::createContext()
QGuiApplication::sync(); QGuiApplication::sync();
context = std::make_unique<Vulkan::Context>(); context = std::make_unique<Vulkan::Context>();
context->set_preferred_device(config->display_device_index);
#ifdef _WIN32 #ifdef _WIN32
auto hwnd = (HWND)winId(); auto hwnd = (HWND)winId();
if (!context->init_win32(nullptr, hwnd, config->display_device_index)) if (!context->init_win32(nullptr, hwnd, config->display_device_index))
{ goto fail;
context.reset(); if (!context->create_win32_surface(nullptr, hwnd))
return false; goto fail;
} if (!context->swapchain->create())
goto fail;
#else #else
if (platform == "wayland") if (platform == "wayland")
{ {
@ -101,21 +103,31 @@ bool EmuCanvasVulkan::createContext()
auto surface = (wl_surface *)pni->nativeResourceForWindow("surface", main_window->windowHandle()); 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()) }); 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(); 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))
{ if (!context->init_wayland())
context.reset(); goto fail;
return false;
} 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") else if (platform == "xcb")
{ {
auto display = (Display *)pni->nativeResourceForWindow("display", window); auto display = (Display *)pni->nativeResourceForWindow("display", window);
auto xid = (Window)winId(); auto xid = (Window)winId();
if (!context->init_Xlib(display, xid, config->display_device_index))
{ if (!context->init_Xlib())
context.reset(); goto fail;
return false;
} if (!context->create_Xlib_surface(display, xid))
goto fail;
context->wait_idle();
if (!context->swapchain->create())
goto fail;
} }
#endif #endif
@ -130,6 +142,10 @@ bool EmuCanvasVulkan::createContext()
paintEvent(nullptr); paintEvent(nullptr);
return true; return true;
fail:
context.reset();
return false;
} }
void EmuCanvasVulkan::tryLoadShader() void EmuCanvasVulkan::tryLoadShader()