From 7104e01bb3405369fc74a9c747c9038fc93cf6e3 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Mon, 16 Mar 2020 17:48:53 -0300
Subject: [PATCH 1/3] common/dynamic_library: Import and adapt helper from
 Dolphin

---
 src/common/CMakeLists.txt      |   2 +
 src/common/dynamic_library.cpp | 106 +++++++++++++++++++++++++++++++++
 src/common/dynamic_library.h   |  75 +++++++++++++++++++++++
 3 files changed, 183 insertions(+)
 create mode 100644 src/common/dynamic_library.cpp
 create mode 100644 src/common/dynamic_library.h

diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index fbebed715..eeceaa655 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -106,6 +106,8 @@ add_library(common STATIC
     common_funcs.h
     common_paths.h
     common_types.h
+    dynamic_library.cpp
+    dynamic_library.h
     file_util.cpp
     file_util.h
     hash.h
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
new file mode 100644
index 000000000..7ab54e9e4
--- /dev/null
+++ b/src/common/dynamic_library.cpp
@@ -0,0 +1,106 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <string>
+#include <utility>
+
+#include <fmt/format.h>
+
+#include "common/dynamic_library.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+namespace Common {
+
+DynamicLibrary::DynamicLibrary() = default;
+
+DynamicLibrary::DynamicLibrary(const char* filename) {
+    Open(filename);
+}
+
+DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept
+    : handle{std::exchange(rhs.handle, nullptr)} {}
+
+DynamicLibrary& DynamicLibrary::operator=(DynamicLibrary&& rhs) noexcept {
+    Close();
+    handle = std::exchange(rhs.handle, nullptr);
+    return *this;
+}
+
+DynamicLibrary::~DynamicLibrary() {
+    Close();
+}
+
+std::string DynamicLibrary::GetUnprefixedFilename(const char* filename) {
+#if defined(_WIN32)
+    return std::string(filename) + ".dll";
+#elif defined(__APPLE__)
+    return std::string(filename) + ".dylib";
+#else
+    return std::string(filename) + ".so";
+#endif
+}
+
+std::string DynamicLibrary::GetVersionedFilename(const char* libname, int major, int minor) {
+#if defined(_WIN32)
+    if (major >= 0 && minor >= 0)
+        return fmt::format("{}-{}-{}.dll", libname, major, minor);
+    else if (major >= 0)
+        return fmt::format("{}-{}.dll", libname, major);
+    else
+        return fmt::format("{}.dll", libname);
+#elif defined(__APPLE__)
+    const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
+    if (major >= 0 && minor >= 0)
+        return fmt::format("{}{}.{}.{}.dylib", prefix, libname, major, minor);
+    else if (major >= 0)
+        return fmt::format("{}{}.{}.dylib", prefix, libname, major);
+    else
+        return fmt::format("{}{}.dylib", prefix, libname);
+#else
+    const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
+    if (major >= 0 && minor >= 0)
+        return fmt::format("{}{}.so.{}.{}", prefix, libname, major, minor);
+    else if (major >= 0)
+        return fmt::format("{}{}.so.{}", prefix, libname, major);
+    else
+        return fmt::format("{}{}.so", prefix, libname);
+#endif
+}
+
+bool DynamicLibrary::Open(const char* filename) {
+#ifdef _WIN32
+    handle = reinterpret_cast<void*>(LoadLibraryA(filename));
+#else
+    handle = dlopen(filename, RTLD_NOW);
+#endif
+    return handle != nullptr;
+}
+
+void DynamicLibrary::Close() {
+    if (!IsOpen())
+        return;
+
+#ifdef _WIN32
+    FreeLibrary(reinterpret_cast<HMODULE>(handle));
+#else
+    dlclose(handle);
+#endif
+    handle = nullptr;
+}
+
+void* DynamicLibrary::GetSymbolAddress(const char* name) const {
+#ifdef _WIN32
+    return reinterpret_cast<void*>(GetProcAddress(reinterpret_cast<HMODULE>(handle), name));
+#else
+    return reinterpret_cast<void*>(dlsym(handle, name));
+#endif
+}
+
+} // namespace Common
diff --git a/src/common/dynamic_library.h b/src/common/dynamic_library.h
new file mode 100644
index 000000000..2a06372fd
--- /dev/null
+++ b/src/common/dynamic_library.h
@@ -0,0 +1,75 @@
+// Copyright 2019 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+namespace Common {
+
+/**
+ * Provides a platform-independent interface for loading a dynamic library and retrieving symbols.
+ * The interface maintains an internal reference count to allow one handle to be shared between
+ * multiple users.
+ */
+class DynamicLibrary final {
+public:
+    /// Default constructor, does not load a library.
+    explicit DynamicLibrary();
+
+    /// Automatically loads the specified library. Call IsOpen() to check validity before use.
+    explicit DynamicLibrary(const char* filename);
+
+    /// Moves the library.
+    DynamicLibrary(DynamicLibrary&&) noexcept;
+    DynamicLibrary& operator=(DynamicLibrary&&) noexcept;
+
+    /// Delete copies, we can't copy a dynamic library.
+    DynamicLibrary(const DynamicLibrary&) = delete;
+    DynamicLibrary& operator=(const DynamicLibrary&) = delete;
+
+    /// Closes the library.
+    ~DynamicLibrary();
+
+    /// Returns the specified library name with the platform-specific suffix added.
+    static std::string GetUnprefixedFilename(const char* filename);
+
+    /// Returns the specified library name in platform-specific format.
+    /// Major/minor versions will not be included if set to -1.
+    /// If libname already contains the "lib" prefix, it will not be added again.
+    /// Windows: LIBNAME-MAJOR-MINOR.dll
+    /// Linux: libLIBNAME.so.MAJOR.MINOR
+    /// Mac: libLIBNAME.MAJOR.MINOR.dylib
+    static std::string GetVersionedFilename(const char* libname, int major = -1, int minor = -1);
+
+    /// Returns true if a module is loaded, otherwise false.
+    bool IsOpen() const {
+        return handle != nullptr;
+    }
+
+    /// Loads (or replaces) the handle with the specified library file name.
+    /// Returns true if the library was loaded and can be used.
+    bool Open(const char* filename);
+
+    /// Unloads the library, any function pointers from this library are no longer valid.
+    void Close();
+
+    /// Returns the address of the specified symbol (function or variable) as an untyped pointer.
+    /// If the specified symbol does not exist in this library, nullptr is returned.
+    void* GetSymbolAddress(const char* name) const;
+
+    /// Obtains the address of the specified symbol, automatically casting to the correct type.
+    /// Returns true if the symbol was found and assigned, otherwise false.
+    template <typename T>
+    bool GetSymbol(const char* name, T* ptr) const {
+        *ptr = reinterpret_cast<T>(GetSymbolAddress(name));
+        return *ptr != nullptr;
+    }
+
+private:
+    /// Platform-dependent data type representing a dynamic library handle.
+    void* handle = nullptr;
+};
+
+} // namespace Common

From bc1b4b85b01aee30a14c234143e72ef3435f9660 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Tue, 17 Mar 2020 01:39:38 -0300
Subject: [PATCH 2/3] renderer_vulkan: Query device names from the backend

---
 src/video_core/renderer_vulkan/declarations.h |  1 +
 .../renderer_vulkan/renderer_vulkan.cpp       | 69 +++++++++++++++++++
 .../renderer_vulkan/renderer_vulkan.h         |  3 +
 src/yuzu/configuration/configure_graphics.cpp | 41 ++---------
 4 files changed, 79 insertions(+), 35 deletions(-)

diff --git a/src/video_core/renderer_vulkan/declarations.h b/src/video_core/renderer_vulkan/declarations.h
index 323bf6b39..d2a1140c1 100644
--- a/src/video_core/renderer_vulkan/declarations.h
+++ b/src/video_core/renderer_vulkan/declarations.h
@@ -39,6 +39,7 @@ using UniqueFence = UniqueHandle<vk::Fence>;
 using UniqueFramebuffer = UniqueHandle<vk::Framebuffer>;
 using UniqueImage = UniqueHandle<vk::Image>;
 using UniqueImageView = UniqueHandle<vk::ImageView>;
+using UniqueInstance = UniqueHandle<vk::Instance>;
 using UniqueIndirectCommandsLayoutNVX = UniqueHandle<vk::IndirectCommandsLayoutNVX>;
 using UniqueObjectTableNVX = UniqueHandle<vk::ObjectTableNVX>;
 using UniquePipeline = UniqueHandle<vk::Pipeline>;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 6953aaafe..9c323a1aa 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -9,6 +9,7 @@
 #include <fmt/format.h>
 
 #include "common/assert.h"
+#include "common/dynamic_library.h"
 #include "common/logging/log.h"
 #include "common/telemetry.h"
 #include "core/core.h"
@@ -53,6 +54,45 @@ VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity_,
     return VK_FALSE;
 }
 
+Common::DynamicLibrary OpenVulkanLibrary() {
+    Common::DynamicLibrary library;
+#ifdef __APPLE__
+    // Check if a path to a specific Vulkan library has been specified.
+    char* libvulkan_env = getenv("LIBVULKAN_PATH");
+    if (!libvulkan_env || !library.Open(libvulkan_env)) {
+        // Use the libvulkan.dylib from the application bundle.
+        std::string filename = File::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
+        library.Open(filename.c_str());
+    }
+#else
+    std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
+    if (!library.Open(filename.c_str())) {
+        // Android devices may not have libvulkan.so.1, only libvulkan.so.
+        filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
+        library.Open(filename.c_str());
+    }
+#endif
+    return library;
+}
+
+UniqueInstance CreateInstance(Common::DynamicLibrary& library, vk::DispatchLoaderDynamic& dld) {
+    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+    if (!library.GetSymbol("vkGetInstanceProcAddr", &vkGetInstanceProcAddr)) {
+        return UniqueInstance{};
+    }
+    dld.init(vkGetInstanceProcAddr);
+
+    const vk::ApplicationInfo application_info("yuzu", VK_MAKE_VERSION(0, 1, 0), "yuzu",
+                                               VK_MAKE_VERSION(0, 1, 0), VK_API_VERSION_1_1);
+    const vk::InstanceCreateInfo instance_ci({}, &application_info, 0, nullptr, 0, nullptr);
+    vk::Instance unsafe_instance;
+    if (vk::createInstance(&instance_ci, nullptr, &unsafe_instance, dld) != vk::Result::eSuccess) {
+        return UniqueInstance{};
+    }
+    dld.init(unsafe_instance, vkGetInstanceProcAddr);
+    return UniqueInstance(unsafe_instance, {nullptr, dld});
+}
+
 std::string GetReadableVersion(u32 version) {
     return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
                        VK_VERSION_PATCH(version));
@@ -276,4 +316,33 @@ void RendererVulkan::Report() const {
     telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
 }
 
+std::vector<std::string> RendererVulkan::EnumerateDevices() {
+    Common::DynamicLibrary library = OpenVulkanLibrary();
+    if (!library.IsOpen()) {
+        return {};
+    }
+    vk::DispatchLoaderDynamic dld;
+    UniqueInstance instance = CreateInstance(library, dld);
+    if (!instance) {
+        return {};
+    }
+
+    u32 num_devices;
+    if (instance->enumeratePhysicalDevices(&num_devices, nullptr, dld) != vk::Result::eSuccess) {
+        return {};
+    }
+    std::vector<vk::PhysicalDevice> devices(num_devices);
+    if (instance->enumeratePhysicalDevices(&num_devices, devices.data(), dld) !=
+        vk::Result::eSuccess) {
+        return {};
+    }
+
+    std::vector<std::string> names;
+    names.reserve(num_devices);
+    for (auto& device : devices) {
+        names.push_back(device.getProperties(dld).deviceName);
+    }
+    return names;
+}
+
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index d14384e79..7a17c546d 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <optional>
+#include <string>
 #include <vector>
 
 #include "video_core/renderer_base.h"
@@ -44,6 +45,8 @@ public:
     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
     bool TryPresent(int timeout_ms) override;
 
+    static std::vector<std::string> EnumerateDevices();
+
 private:
     std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback(
         const vk::DispatchLoaderDynamic& dldi);
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index a821c7b3c..d29332033 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -14,6 +14,9 @@
 #include "core/settings.h"
 #include "ui_configure_graphics.h"
 #include "yuzu/configuration/configure_graphics.h"
+#ifdef HAS_VULKAN
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+#endif
 
 namespace {
 enum class Resolution : int {
@@ -165,41 +168,9 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
 
 void ConfigureGraphics::RetrieveVulkanDevices() {
 #ifdef HAS_VULKAN
-    QVulkanInstance instance;
-    instance.setApiVersion(QVersionNumber(1, 1, 0));
-    if (!instance.create()) {
-        LOG_INFO(Frontend, "Vulkan 1.1 not available");
-        return;
-    }
-    const auto vkEnumeratePhysicalDevices{reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
-        instance.getInstanceProcAddr("vkEnumeratePhysicalDevices"))};
-    if (vkEnumeratePhysicalDevices == nullptr) {
-        LOG_INFO(Frontend, "Failed to get pointer to vkEnumeratePhysicalDevices");
-        return;
-    }
-    u32 physical_device_count;
-    if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count, nullptr) !=
-        VK_SUCCESS) {
-        LOG_INFO(Frontend, "Failed to get physical devices count");
-        return;
-    }
-    std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
-    if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count,
-                                   physical_devices.data()) != VK_SUCCESS) {
-        LOG_INFO(Frontend, "Failed to get physical devices");
-        return;
-    }
-
-    const auto vkGetPhysicalDeviceProperties{reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
-        instance.getInstanceProcAddr("vkGetPhysicalDeviceProperties"))};
-    if (vkGetPhysicalDeviceProperties == nullptr) {
-        LOG_INFO(Frontend, "Failed to get pointer to vkGetPhysicalDeviceProperties");
-        return;
-    }
-    for (const auto physical_device : physical_devices) {
-        VkPhysicalDeviceProperties properties;
-        vkGetPhysicalDeviceProperties(physical_device, &properties);
-        vulkan_devices.push_back(QString::fromUtf8(properties.deviceName));
+    vulkan_devices.clear();
+    for (auto& name : Vulkan::RendererVulkan::EnumerateDevices()) {
+        vulkan_devices.push_back(QString::fromStdString(name));
     }
 #endif
 }

From bf1d66b7c074c02aa8148f2edbdc959082c229e1 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 2 Apr 2020 02:32:58 -0300
Subject: [PATCH 3/3] yuzu: Drop SDL2 and Qt frontend Vulkan requirements

Create Vulkan instances and surfaces from the Vulkan backend.
---
 src/core/frontend/emu_window.h                |  41 +++-
 src/video_core/renderer_vulkan/declarations.h |   1 +
 .../renderer_vulkan/renderer_vulkan.cpp       | 217 ++++++++++++++----
 .../renderer_vulkan/renderer_vulkan.h         |  16 +-
 src/video_core/renderer_vulkan/vk_device.cpp  |  92 ++++----
 src/video_core/renderer_vulkan/vk_device.h    |  21 +-
 src/yuzu/CMakeLists.txt                       |   4 +
 src/yuzu/bootmanager.cpp                      |  92 ++++----
 src/yuzu/bootmanager.h                        |   9 -
 src/yuzu/configuration/configure_graphics.cpp |   1 +
 .../emu_window/emu_window_sdl2_gl.cpp         |   6 -
 src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h  |   4 -
 .../emu_window/emu_window_sdl2_vk.cpp         | 159 ++++---------
 src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h  |  18 +-
 .../emu_window/emu_window_sdl2_hide.cpp       |   4 -
 .../emu_window/emu_window_sdl2_hide.h         |   4 -
 16 files changed, 373 insertions(+), 316 deletions(-)

diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 72294d4d8..13aa14934 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -12,6 +12,15 @@
 
 namespace Core::Frontend {
 
+/// Information for the Graphics Backends signifying what type of screen pointer is in
+/// WindowInformation
+enum class WindowSystemType {
+    Headless,
+    Windows,
+    X11,
+    Wayland,
+};
+
 /**
  * Represents a drawing context that supports graphics operations.
  */
@@ -76,6 +85,23 @@ public:
         std::pair<unsigned, unsigned> min_client_area_size;
     };
 
+    /// Data describing host window system information
+    struct WindowSystemInfo {
+        // Window system type. Determines which GL context or Vulkan WSI is used.
+        WindowSystemType type = WindowSystemType::Headless;
+
+        // Connection to a display server. This is used on X11 and Wayland platforms.
+        void* display_connection = nullptr;
+
+        // Render surface. This is a pointer to the native window handle, which depends
+        // on the platform. e.g. HWND for Windows, Window for X11. If the surface is
+        // set to nullptr, the video backend will run in headless mode.
+        void* render_surface = nullptr;
+
+        // Scale of the render surface. For hidpi systems, this will be >1.
+        float render_surface_scale = 1.0f;
+    };
+
     /// Polls window events
     virtual void PollEvents() = 0;
 
@@ -87,10 +113,6 @@ public:
     /// Returns if window is shown (not minimized)
     virtual bool IsShown() const = 0;
 
-    /// Retrieves Vulkan specific handlers from the window
-    virtual void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                        void* surface) const = 0;
-
     /**
      * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
      * @param framebuffer_x Framebuffer x-coordinate that was pressed
@@ -127,6 +149,13 @@ public:
         config = val;
     }
 
+    /**
+     * Returns system information about the drawing area.
+     */
+    const WindowSystemInfo& GetWindowInfo() const {
+        return window_info;
+    }
+
     /**
      * Gets the framebuffer layout (width, height, and screen regions)
      * @note This method is thread-safe
@@ -142,7 +171,7 @@ public:
     void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
 
 protected:
-    EmuWindow();
+    explicit EmuWindow();
     virtual ~EmuWindow();
 
     /**
@@ -179,6 +208,8 @@ protected:
         client_area_height = size.second;
     }
 
+    WindowSystemInfo window_info;
+
 private:
     /**
      * Handler called when the minimal client area was requested to be changed via SetConfig.
diff --git a/src/video_core/renderer_vulkan/declarations.h b/src/video_core/renderer_vulkan/declarations.h
index d2a1140c1..89a035ca4 100644
--- a/src/video_core/renderer_vulkan/declarations.h
+++ b/src/video_core/renderer_vulkan/declarations.h
@@ -51,6 +51,7 @@ using UniqueSampler = UniqueHandle<vk::Sampler>;
 using UniqueSamplerYcbcrConversion = UniqueHandle<vk::SamplerYcbcrConversion>;
 using UniqueSemaphore = UniqueHandle<vk::Semaphore>;
 using UniqueShaderModule = UniqueHandle<vk::ShaderModule>;
+using UniqueSurfaceKHR = UniqueHandle<vk::SurfaceKHR>;
 using UniqueSwapchainKHR = UniqueHandle<vk::SwapchainKHR>;
 using UniqueValidationCacheEXT = UniqueHandle<vk::ValidationCacheEXT>;
 using UniqueDebugReportCallbackEXT = UniqueHandle<vk::DebugReportCallbackEXT>;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 9c323a1aa..9cdb4b627 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -2,8 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <algorithm>
+#include <array>
+#include <cstring>
 #include <memory>
 #include <optional>
+#include <string>
 #include <vector>
 
 #include <fmt/format.h>
@@ -31,15 +35,30 @@
 #include "video_core/renderer_vulkan/vk_state_tracker.h"
 #include "video_core/renderer_vulkan/vk_swapchain.h"
 
+// Include these late to avoid changing Vulkan-Hpp's dynamic dispatcher size
+#ifdef _WIN32
+#include <windows.h>
+// ensure include order
+#include <vulkan/vulkan_win32.h>
+#endif
+
+#ifdef __linux__
+#include <X11/Xlib.h>
+#include <vulkan/vulkan_wayland.h>
+#include <vulkan/vulkan_xlib.h>
+#endif
+
 namespace Vulkan {
 
 namespace {
 
+using Core::Frontend::WindowSystemType;
+
 VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity_,
                        VkDebugUtilsMessageTypeFlagsEXT type,
                        const VkDebugUtilsMessengerCallbackDataEXT* data,
                        [[maybe_unused]] void* user_data) {
-    const vk::DebugUtilsMessageSeverityFlagBitsEXT severity{severity_};
+    const auto severity{static_cast<vk::DebugUtilsMessageSeverityFlagBitsEXT>(severity_)};
     const char* message{data->pMessage};
 
     if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
@@ -75,21 +94,86 @@ Common::DynamicLibrary OpenVulkanLibrary() {
     return library;
 }
 
-UniqueInstance CreateInstance(Common::DynamicLibrary& library, vk::DispatchLoaderDynamic& dld) {
+UniqueInstance CreateInstance(Common::DynamicLibrary& library, vk::DispatchLoaderDynamic& dld,
+                              WindowSystemType window_type = WindowSystemType::Headless,
+                              bool enable_layers = false) {
+    if (!library.IsOpen()) {
+        LOG_ERROR(Render_Vulkan, "Vulkan library not available");
+        return UniqueInstance{};
+    }
     PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
     if (!library.GetSymbol("vkGetInstanceProcAddr", &vkGetInstanceProcAddr)) {
+        LOG_ERROR(Render_Vulkan, "vkGetInstanceProcAddr not present in Vulkan");
         return UniqueInstance{};
     }
     dld.init(vkGetInstanceProcAddr);
 
-    const vk::ApplicationInfo application_info("yuzu", VK_MAKE_VERSION(0, 1, 0), "yuzu",
-                                               VK_MAKE_VERSION(0, 1, 0), VK_API_VERSION_1_1);
-    const vk::InstanceCreateInfo instance_ci({}, &application_info, 0, nullptr, 0, nullptr);
-    vk::Instance unsafe_instance;
-    if (vk::createInstance(&instance_ci, nullptr, &unsafe_instance, dld) != vk::Result::eSuccess) {
+    std::vector<const char*> extensions;
+    extensions.reserve(4);
+    switch (window_type) {
+    case Core::Frontend::WindowSystemType::Headless:
+        break;
+#ifdef _WIN32
+    case Core::Frontend::WindowSystemType::Windows:
+        extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+        break;
+#endif
+#ifdef __linux__
+    case Core::Frontend::WindowSystemType::X11:
+        extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
+        break;
+    case Core::Frontend::WindowSystemType::Wayland:
+        extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
+        break;
+#endif
+    default:
+        LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
+        break;
+    }
+    if (window_type != Core::Frontend::WindowSystemType::Headless) {
+        extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
+    }
+    if (enable_layers) {
+        extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+    }
+
+    u32 num_properties;
+    if (vk::enumerateInstanceExtensionProperties(nullptr, &num_properties, nullptr, dld) !=
+        vk::Result::eSuccess) {
+        LOG_ERROR(Render_Vulkan, "Failed to query number of extension properties");
         return UniqueInstance{};
     }
-    dld.init(unsafe_instance, vkGetInstanceProcAddr);
+    std::vector<vk::ExtensionProperties> properties(num_properties);
+    if (vk::enumerateInstanceExtensionProperties(nullptr, &num_properties, properties.data(),
+                                                 dld) != vk::Result::eSuccess) {
+        LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
+        return UniqueInstance{};
+    }
+
+    for (const char* extension : extensions) {
+        const auto it =
+            std::find_if(properties.begin(), properties.end(), [extension](const auto& prop) {
+                return !std::strcmp(extension, prop.extensionName);
+            });
+        if (it == properties.end()) {
+            LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension);
+            return UniqueInstance{};
+        }
+    }
+
+    const vk::ApplicationInfo application_info("yuzu Emulator", VK_MAKE_VERSION(0, 1, 0),
+                                               "yuzu Emulator", VK_MAKE_VERSION(0, 1, 0),
+                                               VK_API_VERSION_1_1);
+    const std::array layers = {"VK_LAYER_LUNARG_standard_validation"};
+    const vk::InstanceCreateInfo instance_ci(
+        {}, &application_info, enable_layers ? static_cast<u32>(layers.size()) : 0, layers.data(),
+        static_cast<u32>(extensions.size()), extensions.data());
+    vk::Instance unsafe_instance;
+    if (vk::createInstance(&instance_ci, nullptr, &unsafe_instance, dld) != vk::Result::eSuccess) {
+        LOG_ERROR(Render_Vulkan, "Failed to create Vulkan instance");
+        return UniqueInstance{};
+    }
+    dld.init(unsafe_instance);
     return UniqueInstance(unsafe_instance, {nullptr, dld});
 }
 
@@ -187,27 +271,12 @@ bool RendererVulkan::TryPresent(int /*timeout_ms*/) {
 }
 
 bool RendererVulkan::Init() {
-    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
-    render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface);
-    const vk::DispatchLoaderDynamic dldi(instance, vkGetInstanceProcAddr);
-
-    std::optional<vk::DebugUtilsMessengerEXT> callback;
-    if (Settings::values.renderer_debug && dldi.vkCreateDebugUtilsMessengerEXT) {
-        callback = CreateDebugCallback(dldi);
-        if (!callback) {
-            return false;
-        }
-    }
-
-    if (!PickDevices(dldi)) {
-        if (callback) {
-            instance.destroy(*callback, nullptr, dldi);
-        }
+    library = OpenVulkanLibrary();
+    instance = CreateInstance(library, dld, render_window.GetWindowInfo().type,
+                              Settings::values.renderer_debug);
+    if (!instance || !CreateDebugCallback() || !CreateSurface() || !PickDevices()) {
         return false;
     }
-    debug_callback = UniqueDebugUtilsMessengerEXT(
-        *callback, vk::ObjectDestroy<vk::Instance, vk::DispatchLoaderDynamic>(
-                       instance, nullptr, device->GetDispatchLoader()));
 
     Report();
 
@@ -216,7 +285,7 @@ bool RendererVulkan::Init() {
     resource_manager = std::make_unique<VKResourceManager>(*device);
 
     const auto& framebuffer = render_window.GetFramebufferLayout();
-    swapchain = std::make_unique<VKSwapchain>(surface, *device);
+    swapchain = std::make_unique<VKSwapchain>(*surface, *device);
     swapchain->Create(framebuffer.width, framebuffer.height, false);
 
     state_tracker = std::make_unique<StateTracker>(system);
@@ -253,8 +322,10 @@ void RendererVulkan::ShutDown() {
     device.reset();
 }
 
-std::optional<vk::DebugUtilsMessengerEXT> RendererVulkan::CreateDebugCallback(
-    const vk::DispatchLoaderDynamic& dldi) {
+bool RendererVulkan::CreateDebugCallback() {
+    if (!Settings::values.renderer_debug) {
+        return true;
+    }
     const vk::DebugUtilsMessengerCreateInfoEXT callback_ci(
         {},
         vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
@@ -265,32 +336,88 @@ std::optional<vk::DebugUtilsMessengerEXT> RendererVulkan::CreateDebugCallback(
             vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
             vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
         &DebugCallback, nullptr);
-    vk::DebugUtilsMessengerEXT callback;
-    if (instance.createDebugUtilsMessengerEXT(&callback_ci, nullptr, &callback, dldi) !=
+    vk::DebugUtilsMessengerEXT unsafe_callback;
+    if (instance->createDebugUtilsMessengerEXT(&callback_ci, nullptr, &unsafe_callback, dld) !=
         vk::Result::eSuccess) {
         LOG_ERROR(Render_Vulkan, "Failed to create debug callback");
-        return {};
+        return false;
     }
-    return callback;
+    debug_callback = UniqueDebugUtilsMessengerEXT(unsafe_callback, {*instance, nullptr, dld});
+    return true;
 }
 
-bool RendererVulkan::PickDevices(const vk::DispatchLoaderDynamic& dldi) {
-    const auto devices = instance.enumeratePhysicalDevices(dldi);
+bool RendererVulkan::CreateSurface() {
+    [[maybe_unused]] const auto& window_info = render_window.GetWindowInfo();
+    VkSurfaceKHR unsafe_surface = nullptr;
+
+#ifdef _WIN32
+    if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
+        const HWND hWnd = static_cast<HWND>(window_info.render_surface);
+        const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+                                                   nullptr, 0, nullptr, hWnd};
+        const auto vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(
+            dld.vkGetInstanceProcAddr(*instance, "vkCreateWin32SurfaceKHR"));
+        if (!vkCreateWin32SurfaceKHR || vkCreateWin32SurfaceKHR(instance.get(), &win32_ci, nullptr,
+                                                                &unsafe_surface) != VK_SUCCESS) {
+            LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface");
+            return false;
+        }
+    }
+#endif
+#ifdef __linux__
+    if (window_info.type == Core::Frontend::WindowSystemType::X11) {
+        const VkXlibSurfaceCreateInfoKHR xlib_ci{
+            VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0,
+            static_cast<Display*>(window_info.display_connection),
+            reinterpret_cast<Window>(window_info.render_surface)};
+        const auto vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>(
+            dld.vkGetInstanceProcAddr(*instance, "vkCreateXlibSurfaceKHR"));
+        if (!vkCreateXlibSurfaceKHR || vkCreateXlibSurfaceKHR(instance.get(), &xlib_ci, nullptr,
+                                                              &unsafe_surface) != VK_SUCCESS) {
+            LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface");
+            return false;
+        }
+    }
+    if (window_info.type == Core::Frontend::WindowSystemType::Wayland) {
+        const VkWaylandSurfaceCreateInfoKHR wayland_ci{
+            VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
+            static_cast<wl_display*>(window_info.display_connection),
+            static_cast<wl_surface*>(window_info.render_surface)};
+        const auto vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>(
+            dld.vkGetInstanceProcAddr(*instance, "vkCreateWaylandSurfaceKHR"));
+        if (!vkCreateWaylandSurfaceKHR ||
+            vkCreateWaylandSurfaceKHR(instance.get(), &wayland_ci, nullptr, &unsafe_surface) !=
+                VK_SUCCESS) {
+            LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface");
+            return false;
+        }
+    }
+#endif
+    if (!unsafe_surface) {
+        LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
+        return false;
+    }
+
+    surface = UniqueSurfaceKHR(unsafe_surface, {*instance, nullptr, dld});
+    return true;
+}
+
+bool RendererVulkan::PickDevices() {
+    const auto devices = instance->enumeratePhysicalDevices(dld);
 
-    // TODO(Rodrigo): Choose device from config file
     const s32 device_index = Settings::values.vulkan_device;
     if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) {
         LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
         return false;
     }
-    const vk::PhysicalDevice physical_device = devices[device_index];
+    const vk::PhysicalDevice physical_device = devices[static_cast<std::size_t>(device_index)];
 
-    if (!VKDevice::IsSuitable(dldi, physical_device, surface)) {
+    if (!VKDevice::IsSuitable(physical_device, *surface, dld)) {
         return false;
     }
 
-    device = std::make_unique<VKDevice>(dldi, physical_device, surface);
-    return device->Create(dldi, instance);
+    device = std::make_unique<VKDevice>(dld, physical_device, *surface);
+    return device->Create(*instance);
 }
 
 void RendererVulkan::Report() const {
@@ -317,11 +444,11 @@ void RendererVulkan::Report() const {
 }
 
 std::vector<std::string> RendererVulkan::EnumerateDevices() {
+    // Avoid putting DispatchLoaderDynamic, it's too large
+    auto dld_memory = std::make_unique<vk::DispatchLoaderDynamic>();
+    auto& dld = *dld_memory;
+
     Common::DynamicLibrary library = OpenVulkanLibrary();
-    if (!library.IsOpen()) {
-        return {};
-    }
-    vk::DispatchLoaderDynamic dld;
     UniqueInstance instance = CreateInstance(library, dld);
     if (!instance) {
         return {};
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 7a17c546d..42e253de5 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -9,6 +9,8 @@
 #include <string>
 #include <vector>
 
+#include "common/dynamic_library.h"
+
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_vulkan/declarations.h"
 
@@ -48,17 +50,21 @@ public:
     static std::vector<std::string> EnumerateDevices();
 
 private:
-    std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback(
-        const vk::DispatchLoaderDynamic& dldi);
+    bool CreateDebugCallback();
 
-    bool PickDevices(const vk::DispatchLoaderDynamic& dldi);
+    bool CreateSurface();
+
+    bool PickDevices();
 
     void Report() const;
 
     Core::System& system;
 
-    vk::Instance instance;
-    vk::SurfaceKHR surface;
+    Common::DynamicLibrary library;
+    vk::DispatchLoaderDynamic dld;
+
+    UniqueInstance instance;
+    UniqueSurfaceKHR surface;
 
     VKScreenInfo screen_info;
 
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 7aafb5e59..6f4ae9132 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -10,6 +10,7 @@
 #include <string_view>
 #include <thread>
 #include <vector>
+
 #include "common/assert.h"
 #include "core/settings.h"
 #include "video_core/renderer_vulkan/declarations.h"
@@ -35,20 +36,20 @@ void SetNext(void**& next, T& data) {
 }
 
 template <typename T>
-T GetFeatures(vk::PhysicalDevice physical, const vk::DispatchLoaderDynamic& dldi) {
+T GetFeatures(vk::PhysicalDevice physical, const vk::DispatchLoaderDynamic& dld) {
     vk::PhysicalDeviceFeatures2 features;
     T extension_features;
     features.pNext = &extension_features;
-    physical.getFeatures2(&features, dldi);
+    physical.getFeatures2(&features, dld);
     return extension_features;
 }
 
 template <typename T>
-T GetProperties(vk::PhysicalDevice physical, const vk::DispatchLoaderDynamic& dldi) {
+T GetProperties(vk::PhysicalDevice physical, const vk::DispatchLoaderDynamic& dld) {
     vk::PhysicalDeviceProperties2 properties;
     T extension_properties;
     properties.pNext = &extension_properties;
-    physical.getProperties2(&properties, dldi);
+    physical.getProperties2(&properties, dld);
     return extension_properties;
 }
 
@@ -78,19 +79,19 @@ vk::FormatFeatureFlags GetFormatFeatures(vk::FormatProperties properties, Format
 
 } // Anonymous namespace
 
-VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
+VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dld, vk::PhysicalDevice physical,
                    vk::SurfaceKHR surface)
-    : physical{physical}, properties{physical.getProperties(dldi)},
-      format_properties{GetFormatProperties(dldi, physical)} {
-    SetupFamilies(dldi, surface);
-    SetupFeatures(dldi);
+    : dld{dld}, physical{physical}, properties{physical.getProperties(dld)},
+      format_properties{GetFormatProperties(dld, physical)} {
+    SetupFamilies(surface);
+    SetupFeatures();
 }
 
 VKDevice::~VKDevice() = default;
 
-bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) {
+bool VKDevice::Create(vk::Instance instance) {
     const auto queue_cis = GetDeviceQueueCreateInfos();
-    const std::vector extensions = LoadExtensions(dldi);
+    const std::vector extensions = LoadExtensions();
 
     vk::PhysicalDeviceFeatures2 features2;
     void** next = &features2.pNext;
@@ -165,15 +166,13 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan
                                    nullptr);
     device_ci.pNext = &features2;
 
-    vk::Device dummy_logical;
-    if (physical.createDevice(&device_ci, nullptr, &dummy_logical, dldi) != vk::Result::eSuccess) {
+    vk::Device unsafe_logical;
+    if (physical.createDevice(&device_ci, nullptr, &unsafe_logical, dld) != vk::Result::eSuccess) {
         LOG_CRITICAL(Render_Vulkan, "Logical device failed to be created!");
         return false;
     }
-
-    dld.init(instance, dldi.vkGetInstanceProcAddr, dummy_logical, dldi.vkGetDeviceProcAddr);
-    logical = UniqueDevice(
-        dummy_logical, vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dld));
+    dld.init(instance, dld.vkGetInstanceProcAddr, unsafe_logical);
+    logical = UniqueDevice(unsafe_logical, {nullptr, dld});
 
     CollectTelemetryParameters();
 
@@ -235,8 +234,8 @@ void VKDevice::ReportLoss() const {
     // *(VKGraphicsPipeline*)data[0]
 }
 
-bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features,
-                                      const vk::DispatchLoaderDynamic& dldi) const {
+bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features) const {
+    // Disable for now to avoid converting ASTC twice.
     static constexpr std::array astc_formats = {
         vk::Format::eAstc4x4UnormBlock,   vk::Format::eAstc4x4SrgbBlock,
         vk::Format::eAstc5x4UnormBlock,   vk::Format::eAstc5x4SrgbBlock,
@@ -260,7 +259,7 @@ bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features
         vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc |
         vk::FormatFeatureFlagBits::eTransferDst};
     for (const auto format : astc_formats) {
-        const auto format_properties{physical.getFormatProperties(format, dldi)};
+        const auto format_properties{physical.getFormatProperties(format, dld)};
         if (!(format_properties.optimalTilingFeatures & format_feature_usage)) {
             return false;
         }
@@ -279,11 +278,9 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag
     return (supported_usage & wanted_usage) == wanted_usage;
 }
 
-bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
-                          vk::SurfaceKHR surface) {
-    bool is_suitable = true;
-
-    constexpr std::array required_extensions = {
+bool VKDevice::IsSuitable(vk::PhysicalDevice physical, vk::SurfaceKHR surface,
+                          const vk::DispatchLoaderDynamic& dld) {
+    static constexpr std::array required_extensions = {
         VK_KHR_SWAPCHAIN_EXTENSION_NAME,
         VK_KHR_16BIT_STORAGE_EXTENSION_NAME,
         VK_KHR_8BIT_STORAGE_EXTENSION_NAME,
@@ -293,9 +290,10 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
         VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
         VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
     };
+    bool is_suitable = true;
     std::bitset<required_extensions.size()> available_extensions{};
 
-    for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
+    for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dld)) {
         for (std::size_t i = 0; i < required_extensions.size(); ++i) {
             if (available_extensions[i]) {
                 continue;
@@ -315,7 +313,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
     }
 
     bool has_graphics{}, has_present{};
-    const auto queue_family_properties = physical.getQueueFamilyProperties(dldi);
+    const auto queue_family_properties = physical.getQueueFamilyProperties(dld);
     for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) {
         const auto& family = queue_family_properties[i];
         if (family.queueCount == 0) {
@@ -323,7 +321,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
         }
         has_graphics |=
             (family.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlagBits>(0);
-        has_present |= physical.getSurfaceSupportKHR(i, surface, dldi) != 0;
+        has_present |= physical.getSurfaceSupportKHR(i, surface, dld) != 0;
     }
     if (!has_graphics || !has_present) {
         LOG_ERROR(Render_Vulkan, "Device lacks a graphics and present queue");
@@ -331,7 +329,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
     }
 
     // TODO(Rodrigo): Check if the device matches all requeriments.
-    const auto properties{physical.getProperties(dldi)};
+    const auto properties{physical.getProperties(dld)};
     const auto& limits{properties.limits};
 
     constexpr u32 required_ubo_size = 65536;
@@ -348,7 +346,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
         is_suitable = false;
     }
 
-    const auto features{physical.getFeatures(dldi)};
+    const auto features{physical.getFeatures(dld)};
     const std::array feature_report = {
         std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"),
         std::make_pair(features.independentBlend, "independentBlend"),
@@ -380,7 +378,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
     return is_suitable;
 }
 
-std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) {
+std::vector<const char*> VKDevice::LoadExtensions() {
     std::vector<const char*> extensions;
     const auto Test = [&](const vk::ExtensionProperties& extension,
                           std::optional<std::reference_wrapper<bool>> status, const char* name,
@@ -411,7 +409,7 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
     bool has_khr_shader_float16_int8{};
     bool has_ext_subgroup_size_control{};
     bool has_ext_transform_feedback{};
-    for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
+    for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dld)) {
         Test(extension, khr_uniform_buffer_standard_layout,
              VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true);
         Test(extension, has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
@@ -433,15 +431,15 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
 
     if (has_khr_shader_float16_int8) {
         is_float16_supported =
-            GetFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(physical, dldi).shaderFloat16;
+            GetFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(physical, dld).shaderFloat16;
         extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
     }
 
     if (has_ext_subgroup_size_control) {
         const auto features =
-            GetFeatures<vk::PhysicalDeviceSubgroupSizeControlFeaturesEXT>(physical, dldi);
+            GetFeatures<vk::PhysicalDeviceSubgroupSizeControlFeaturesEXT>(physical, dld);
         const auto properties =
-            GetProperties<vk::PhysicalDeviceSubgroupSizeControlPropertiesEXT>(physical, dldi);
+            GetProperties<vk::PhysicalDeviceSubgroupSizeControlPropertiesEXT>(physical, dld);
 
         is_warp_potentially_bigger = properties.maxSubgroupSize > GuestWarpSize;
 
@@ -456,9 +454,9 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
 
     if (has_ext_transform_feedback) {
         const auto features =
-            GetFeatures<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>(physical, dldi);
+            GetFeatures<vk::PhysicalDeviceTransformFeedbackFeaturesEXT>(physical, dld);
         const auto properties =
-            GetProperties<vk::PhysicalDeviceTransformFeedbackPropertiesEXT>(physical, dldi);
+            GetProperties<vk::PhysicalDeviceTransformFeedbackPropertiesEXT>(physical, dld);
 
         if (features.transformFeedback && features.geometryStreams &&
             properties.maxTransformFeedbackStreams >= 4 && properties.maxTransformFeedbackBuffers &&
@@ -471,10 +469,10 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
     return extensions;
 }
 
-void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceKHR surface) {
+void VKDevice::SetupFamilies(vk::SurfaceKHR surface) {
     std::optional<u32> graphics_family_, present_family_;
 
-    const auto queue_family_properties = physical.getQueueFamilyProperties(dldi);
+    const auto queue_family_properties = physical.getQueueFamilyProperties(dld);
     for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) {
         if (graphics_family_ && present_family_)
             break;
@@ -483,10 +481,12 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK
         if (queue_family.queueCount == 0)
             continue;
 
-        if (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)
+        if (queue_family.queueFlags & vk::QueueFlagBits::eGraphics) {
             graphics_family_ = i;
-        if (physical.getSurfaceSupportKHR(i, surface, dldi))
+        }
+        if (physical.getSurfaceSupportKHR(i, surface, dld)) {
             present_family_ = i;
+        }
     }
     ASSERT(graphics_family_ && present_family_);
 
@@ -494,10 +494,10 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK
     present_family = *present_family_;
 }
 
-void VKDevice::SetupFeatures(const vk::DispatchLoaderDynamic& dldi) {
-    const auto supported_features{physical.getFeatures(dldi)};
+void VKDevice::SetupFeatures() {
+    const auto supported_features{physical.getFeatures(dld)};
     is_formatless_image_load_supported = supported_features.shaderStorageImageReadWithoutFormat;
-    is_optimal_astc_supported = IsOptimalAstcSupported(supported_features, dldi);
+    is_optimal_astc_supported = IsOptimalAstcSupported(supported_features);
 }
 
 void VKDevice::CollectTelemetryParameters() {
@@ -525,7 +525,7 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con
 }
 
 std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties(
-    const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) {
+    const vk::DispatchLoaderDynamic& dld, vk::PhysicalDevice physical) {
     static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32,
                                         vk::Format::eA8B8G8R8UintPack32,
                                         vk::Format::eA8B8G8R8SnormPack32,
@@ -606,7 +606,7 @@ std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperti
                                         vk::Format::eE5B9G9R9UfloatPack32};
     std::unordered_map<vk::Format, vk::FormatProperties> format_properties;
     for (const auto format : formats) {
-        format_properties.emplace(format, physical.getFormatProperties(format, dldi));
+        format_properties.emplace(format, physical.getFormatProperties(format, dld));
     }
     return format_properties;
 }
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 6e656517f..d9d809852 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -22,12 +22,12 @@ const u32 GuestWarpSize = 32;
 /// Handles data specific to a physical device.
 class VKDevice final {
 public:
-    explicit VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
+    explicit VKDevice(const vk::DispatchLoaderDynamic& dld, vk::PhysicalDevice physical,
                       vk::SurfaceKHR surface);
     ~VKDevice();
 
     /// Initializes the device. Returns true on success.
-    bool Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance);
+    bool Create(vk::Instance instance);
 
     /**
      * Returns a format supported by the device for the passed requeriments.
@@ -188,18 +188,18 @@ public:
     }
 
     /// Checks if the physical device is suitable.
-    static bool IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
-                           vk::SurfaceKHR surface);
+    static bool IsSuitable(vk::PhysicalDevice physical, vk::SurfaceKHR surface,
+                           const vk::DispatchLoaderDynamic& dld);
 
 private:
     /// Loads extensions into a vector and stores available ones in this object.
-    std::vector<const char*> LoadExtensions(const vk::DispatchLoaderDynamic& dldi);
+    std::vector<const char*> LoadExtensions();
 
     /// Sets up queue families.
-    void SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceKHR surface);
+    void SetupFamilies(vk::SurfaceKHR surface);
 
     /// Sets up device features.
-    void SetupFeatures(const vk::DispatchLoaderDynamic& dldi);
+    void SetupFeatures();
 
     /// Collects telemetry information from the device.
     void CollectTelemetryParameters();
@@ -208,8 +208,7 @@ private:
     std::vector<vk::DeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const;
 
     /// Returns true if ASTC textures are natively supported.
-    bool IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features,
-                                const vk::DispatchLoaderDynamic& dldi) const;
+    bool IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features) const;
 
     /// Returns true if a format is supported.
     bool IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlags wanted_usage,
@@ -217,10 +216,10 @@ private:
 
     /// Returns the device properties for Vulkan formats.
     static std::unordered_map<vk::Format, vk::FormatProperties> GetFormatProperties(
-        const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical);
+        const vk::DispatchLoaderDynamic& dld, vk::PhysicalDevice physical);
 
-    const vk::PhysicalDevice physical;        ///< Physical device.
     vk::DispatchLoaderDynamic dld;            ///< Device function pointers.
+    vk::PhysicalDevice physical;              ///< Physical device.
     vk::PhysicalDeviceProperties properties;  ///< Device properties.
     UniqueDevice logical;                     ///< Logical device.
     vk::Queue graphics_queue;                 ///< Main graphics queue.
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index d34b47b3f..8b9404718 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -150,6 +150,10 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
 target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
 target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
 
+if (ENABLE_VULKAN AND NOT WIN32)
+    target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
+endif()
+
 target_compile_definitions(yuzu PRIVATE
     # Use QStringBuilder for string concatenation to reduce
     # the overall number of temporary strings created.
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index eaded2640..2e54e6788 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -14,8 +14,9 @@
 #include <QScreen>
 #include <QStringList>
 #include <QWindow>
-#ifdef HAS_VULKAN
-#include <QVulkanWindow>
+
+#if !defined(WIN32) && HAS_VULKAN
+#include <qpa/qplatformnativeinterface.h>
 #endif
 
 #include <fmt/format.h>
@@ -238,16 +239,50 @@ private:
 #ifdef HAS_VULKAN
 class VulkanRenderWidget : public RenderWidget {
 public:
-    explicit VulkanRenderWidget(GRenderWindow* parent, QVulkanInstance* instance)
-        : RenderWidget(parent) {
+    explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
         windowHandle()->setSurfaceType(QWindow::VulkanSurface);
-        windowHandle()->setVulkanInstance(instance);
     }
 };
 #endif
 
-GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread)
-    : QWidget(parent_), emu_thread(emu_thread) {
+static Core::Frontend::WindowSystemType GetWindowSystemType() {
+    // Determine WSI type based on Qt platform.
+    QString platform_name = QGuiApplication::platformName();
+    if (platform_name == QStringLiteral("windows"))
+        return Core::Frontend::WindowSystemType::Windows;
+    else if (platform_name == QStringLiteral("xcb"))
+        return Core::Frontend::WindowSystemType::X11;
+    else if (platform_name == QStringLiteral("wayland"))
+        return Core::Frontend::WindowSystemType::Wayland;
+
+    LOG_CRITICAL(Frontend, "Unknown Qt platform!");
+    return Core::Frontend::WindowSystemType::Windows;
+}
+
+static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
+    Core::Frontend::EmuWindow::WindowSystemInfo wsi;
+    wsi.type = GetWindowSystemType();
+
+#ifdef HAS_VULKAN
+    // Our Win32 Qt external doesn't have the private API.
+#if defined(WIN32) || defined(__APPLE__)
+    wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
+#else
+    QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
+    wsi.display_connection = pni->nativeResourceForWindow("display", window);
+    if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
+        wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
+    else
+        wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
+#endif
+    wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
+#endif
+
+    return wsi;
+}
+
+GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
+    : QWidget(parent_), emu_thread(emu_thread_) {
     setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
                        .arg(QString::fromUtf8(Common::g_build_name),
                             QString::fromUtf8(Common::g_scm_branch),
@@ -460,6 +495,9 @@ bool GRenderWindow::InitRenderTarget() {
         break;
     }
 
+    // Update the Window System information with the new render target
+    window_info = GetWindowSystemInfo(child_widget->windowHandle());
+
     child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
     layout()->addWidget(child_widget);
     // Reset minimum required size to avoid resizing issues on the main window after restarting.
@@ -531,30 +569,7 @@ bool GRenderWindow::InitializeOpenGL() {
 
 bool GRenderWindow::InitializeVulkan() {
 #ifdef HAS_VULKAN
-    vk_instance = std::make_unique<QVulkanInstance>();
-    vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
-    vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
-    if (Settings::values.renderer_debug) {
-        const auto supported_layers{vk_instance->supportedLayers()};
-        const bool found =
-            std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
-                constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
-                return layer.name == searched_layer;
-            });
-        if (found) {
-            vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
-            vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
-        }
-    }
-    if (!vk_instance->create()) {
-        QMessageBox::critical(
-            this, tr("Error while initializing Vulkan 1.1!"),
-            tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
-               "latest graphics drivers."));
-        return false;
-    }
-
-    auto child = new VulkanRenderWidget(this, vk_instance.get());
+    auto child = new VulkanRenderWidget(this);
     child_widget = child;
     child_widget->windowHandle()->create();
     main_context = std::make_unique<DummyContext>();
@@ -567,21 +582,6 @@ bool GRenderWindow::InitializeVulkan() {
 #endif
 }
 
-void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                           void* surface) const {
-#ifdef HAS_VULKAN
-    const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
-    const VkInstance instance_copy = vk_instance->vkInstance();
-    const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_widget->windowHandle());
-
-    std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
-    std::memcpy(instance, &instance_copy, sizeof(instance_copy));
-    std::memcpy(surface, &surface_copy, sizeof(surface_copy));
-#else
-    UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
-#endif
-}
-
 bool GRenderWindow::LoadOpenGL() {
     auto context = CreateSharedContext();
     auto scope = context->Acquire();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index d69078df1..3626604ca 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -22,9 +22,6 @@ class GMainWindow;
 class QKeyEvent;
 class QTouchEvent;
 class QStringList;
-#ifdef HAS_VULKAN
-class QVulkanInstance;
-#endif
 
 namespace VideoCore {
 enum class LoadCallbackStage;
@@ -122,8 +119,6 @@ public:
     // EmuWindow implementation.
     void PollEvents() override;
     bool IsShown() const override;
-    void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                void* surface) const override;
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
     void BackupGeometry();
@@ -186,10 +181,6 @@ private:
     // should instead be shared from
     std::shared_ptr<Core::Frontend::GraphicsContext> main_context;
 
-#ifdef HAS_VULKAN
-    std::unique_ptr<QVulkanInstance> vk_instance;
-#endif
-
     /// Temporary storage of the screenshot taken
     QImage screenshot_image;
 
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index d29332033..ea667caef 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -14,6 +14,7 @@
 #include "core/settings.h"
 #include "ui_configure_graphics.h"
 #include "yuzu/configuration/configure_graphics.h"
+
 #ifdef HAS_VULKAN
 #include "video_core/renderer_vulkan/renderer_vulkan.h"
 #endif
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 3522dcf6d..411e7e647 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -156,12 +156,6 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
     SDL_GL_DeleteContext(window_context);
 }
 
-void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                               void* surface) const {
-    // Should not have been called from OpenGL
-    UNREACHABLE();
-}
-
 std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
     return std::make_unique<SDLGLContext>();
 }
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index e092021d7..48bb41683 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -15,10 +15,6 @@ public:
 
     void Present() override;
 
-    /// Ignored in OpenGL
-    void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                void* surface) const override;
-
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
 private:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index 46d053f04..f2990910e 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -2,102 +2,62 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <algorithm>
+#include <cstdlib>
+#include <memory>
 #include <string>
-#include <vector>
-#include <SDL.h>
-#include <SDL_vulkan.h>
+
 #include <fmt/format.h>
-#include <vulkan/vulkan.h>
+
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
 #include "core/settings.h"
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
 
+// Include these late to avoid polluting everything with Xlib macros
+#include <SDL.h>
+#include <SDL_syswm.h>
+
 EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
     : EmuWindow_SDL2{system, fullscreen} {
-    if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
-        LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
-        exit(EXIT_FAILURE);
-    }
-
-    vkGetInstanceProcAddr =
-        reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
-    if (vkGetInstanceProcAddr == nullptr) {
-        LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
-        exit(EXIT_FAILURE);
-    }
-
     const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
                                                  Common::g_scm_branch, Common::g_scm_desc);
     render_window =
-        SDL_CreateWindow(window_title.c_str(),
-                         SDL_WINDOWPOS_UNDEFINED, // x position
-                         SDL_WINDOWPOS_UNDEFINED, // y position
+        SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                          Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
-                         SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_VULKAN);
+                         SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
 
-    const bool use_standard_layers = UseStandardLayers(vkGetInstanceProcAddr);
-
-    u32 extra_ext_count{};
-    if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, NULL)) {
-        LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions count from SDL! {}",
-                     SDL_GetError());
-        exit(1);
+    SDL_SysWMinfo wm;
+    if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) {
+        LOG_CRITICAL(Frontend, "Failed to get information from the window manager");
+        std::exit(EXIT_FAILURE);
     }
 
-    auto extra_ext_names = std::make_unique<const char* []>(extra_ext_count);
-    if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, extra_ext_names.get())) {
-        LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions from SDL! {}", SDL_GetError());
-        exit(1);
-    }
-    std::vector<const char*> enabled_extensions;
-    enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(),
-                              extra_ext_names.get() + extra_ext_count);
-
-    std::vector<const char*> enabled_layers;
-    if (use_standard_layers) {
-        enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
-        enabled_layers.push_back("VK_LAYER_LUNARG_standard_validation");
-    }
-
-    VkApplicationInfo app_info{};
-    app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
-    app_info.apiVersion = VK_API_VERSION_1_1;
-    app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
-    app_info.pApplicationName = "yuzu-emu";
-    app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
-    app_info.pEngineName = "yuzu-emu";
-
-    VkInstanceCreateInfo instance_ci{};
-    instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-    instance_ci.pApplicationInfo = &app_info;
-    instance_ci.enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
-    instance_ci.ppEnabledExtensionNames = enabled_extensions.data();
-    if (Settings::values.renderer_debug) {
-        instance_ci.enabledLayerCount = static_cast<u32>(enabled_layers.size());
-        instance_ci.ppEnabledLayerNames = enabled_layers.data();
-    }
-
-    const auto vkCreateInstance =
-        reinterpret_cast<PFN_vkCreateInstance>(vkGetInstanceProcAddr(nullptr, "vkCreateInstance"));
-    if (vkCreateInstance == nullptr ||
-        vkCreateInstance(&instance_ci, nullptr, &vk_instance) != VK_SUCCESS) {
-        LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!");
-        exit(EXIT_FAILURE);
-    }
-
-    vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(
-        vkGetInstanceProcAddr(vk_instance, "vkDestroyInstance"));
-    if (vkDestroyInstance == nullptr) {
-        LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
-        exit(EXIT_FAILURE);
-    }
-
-    if (!SDL_Vulkan_CreateSurface(render_window, vk_instance, &vk_surface)) {
-        LOG_CRITICAL(Frontend, "Failed to create Vulkan surface! {}", SDL_GetError());
-        exit(EXIT_FAILURE);
+    switch (wm.subsystem) {
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+    case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
+        window_info.type = Core::Frontend::WindowSystemType::Windows;
+        window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window);
+        break;
+#endif
+#ifdef SDL_VIDEO_DRIVER_X11
+    case SDL_SYSWM_TYPE::SDL_SYSWM_X11:
+        window_info.type = Core::Frontend::WindowSystemType::X11;
+        window_info.display_connection = wm.info.x11.display;
+        window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window);
+        break;
+#endif
+#ifdef SDL_VIDEO_DRIVER_WAYLAND
+    case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND:
+        window_info.type = Core::Frontend::WindowSystemType::Wayland;
+        window_info.display_connection = wm.info.wl.display;
+        window_info.render_surface = wm.info.wl.surface;
+        break;
+#endif
+    default:
+        LOG_CRITICAL(Frontend, "Window manager subsystem not implemented");
+        std::exit(EXIT_FAILURE);
     }
 
     OnResize();
@@ -107,51 +67,12 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen)
              Common::g_scm_branch, Common::g_scm_desc);
 }
 
-EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
-    vkDestroyInstance(vk_instance, nullptr);
-}
-
-void EmuWindow_SDL2_VK::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                               void* surface) const {
-    const auto instance_proc_addr = vkGetInstanceProcAddr;
-    std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
-    std::memcpy(instance, &vk_instance, sizeof(vk_instance));
-    std::memcpy(surface, &vk_surface, sizeof(vk_surface));
-}
+EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
 
 std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
     return nullptr;
 }
 
-bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const {
-    if (!Settings::values.renderer_debug) {
-        return false;
-    }
-
-    const auto vkEnumerateInstanceLayerProperties =
-        reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
-            vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"));
-    if (vkEnumerateInstanceLayerProperties == nullptr) {
-        LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
-        return false;
-    }
-
-    u32 available_layers_count{};
-    if (vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr) != VK_SUCCESS) {
-        LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
-        return false;
-    }
-    std::vector<VkLayerProperties> layers(available_layers_count);
-    if (vkEnumerateInstanceLayerProperties(&available_layers_count, layers.data()) != VK_SUCCESS) {
-        LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
-        return false;
-    }
-
-    return std::find_if(layers.begin(), layers.end(), [&](const auto& layer) {
-               return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
-           }) != layers.end();
-}
-
 void EmuWindow_SDL2_VK::Present() {
     // TODO (bunnei): ImplementMe
 }
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index 3dd1f3f61..b8021ebea 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -4,27 +4,21 @@
 
 #pragma once
 
-#include <vulkan/vulkan.h>
+#include <memory>
+
 #include "core/frontend/emu_window.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2.h"
 
+namespace Core {
+class System;
+}
+
 class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
 public:
     explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen);
     ~EmuWindow_SDL2_VK();
 
     void Present() override;
-    void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                void* surface) const override;
 
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
-
-private:
-    bool UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const;
-
-    VkInstance vk_instance{};
-    VkSurfaceKHR vk_surface{};
-
-    PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
-    PFN_vkDestroyInstance vkDestroyInstance{};
 };
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index a837430cc..8584f6671 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -116,10 +116,6 @@ bool EmuWindow_SDL2_Hide::IsShown() const {
     return false;
 }
 
-void EmuWindow_SDL2_Hide::RetrieveVulkanHandlers(void*, void*, void*) const {
-    UNREACHABLE();
-}
-
 class SDLGLContext : public Core::Frontend::GraphicsContext {
 public:
     explicit SDLGLContext() {
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index 9f5d04fca..c13a82df2 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -19,10 +19,6 @@ public:
     /// Whether the screen is being shown or not.
     bool IsShown() const override;
 
-    /// Retrieves Vulkan specific handlers from the window
-    void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
-                                void* surface) const override;
-
     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
 
 private: