diff --git a/src/duckstation-sdl/CMakeLists.txt b/src/duckstation-sdl/CMakeLists.txt
index 4c5d5f29f..ba0029bbb 100644
--- a/src/duckstation-sdl/CMakeLists.txt
+++ b/src/duckstation-sdl/CMakeLists.txt
@@ -7,10 +7,14 @@ add_executable(duckstation-sdl
sdl_host_interface.cpp
sdl_host_interface.h
sdl_key_names.h
+ sdl_util.cpp
+ sdl_util.h
+ sdl_vulkan_host_display.cpp
+ sdl_vulkan_host_display.h
)
target_include_directories(duckstation-sdl PRIVATE ${SDL2_INCLUDE_DIRS})
-target_link_libraries(duckstation-sdl PRIVATE core common imgui nativefiledialog glad frontend-common scmversion ${SDL2_LIBRARIES})
+target_link_libraries(duckstation-sdl PRIVATE core common imgui nativefiledialog glad frontend-common scmversion vulkan-loader ${SDL2_LIBRARIES})
if(WIN32)
target_sources(duckstation-sdl PRIVATE
diff --git a/src/duckstation-sdl/d3d11_host_display.cpp b/src/duckstation-sdl/d3d11_host_display.cpp
index e660e2305..9100d407a 100644
--- a/src/duckstation-sdl/d3d11_host_display.cpp
+++ b/src/duckstation-sdl/d3d11_host_display.cpp
@@ -381,6 +381,7 @@ bool D3D11HostDisplay::CreateImGuiContext()
ImGui_ImplDX11_NewFrame();
ImGui_ImplSDL2_NewFrame(m_window);
+ ImGui::NewFrame();
return true;
}
diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj b/src/duckstation-sdl/duckstation-sdl.vcxproj
index 9041db343..aa8d1d4fa 100644
--- a/src/duckstation-sdl/duckstation-sdl.vcxproj
+++ b/src/duckstation-sdl/duckstation-sdl.vcxproj
@@ -60,6 +60,8 @@
+
+
@@ -68,6 +70,8 @@
+
+
@@ -227,7 +231,7 @@
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -248,7 +252,7 @@
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -269,7 +273,7 @@
WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -293,7 +297,7 @@
WITH_DISCORD_PRESENCE=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
true
false
@@ -316,7 +320,7 @@
MaxSpeed
true
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -339,7 +343,7 @@
MaxSpeed
true
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -363,7 +367,7 @@
MaxSpeed
true
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
false
stdcpp17
@@ -386,7 +390,7 @@
MaxSpeed
true
WITH_DISCORD_PRESENCE=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
- $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
index cb46bd830..58924d54d 100644
--- a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
+++ b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters
@@ -6,6 +6,8 @@
+
+
@@ -14,6 +16,8 @@
+
+
diff --git a/src/duckstation-sdl/opengl_host_display.cpp b/src/duckstation-sdl/opengl_host_display.cpp
index bb5cb4ceb..9ea69e2ed 100644
--- a/src/duckstation-sdl/opengl_host_display.cpp
+++ b/src/duckstation-sdl/opengl_host_display.cpp
@@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/log.h"
#include "imgui_impl_sdl.h"
+#include "sdl_util.h"
#include
#include
#include
@@ -9,17 +10,6 @@
#include
Log_SetChannel(OpenGLHostDisplay);
-#ifdef __APPLE__
-#include
-struct NSView;
-
-static NSView* GetContentViewFromWindow(NSWindow* window)
-{
- // window.contentView
- return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView"));
-}
-#endif
-
class OpenGLDisplayWidgetTexture : public HostDisplayTexture
{
public:
@@ -220,52 +210,11 @@ static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLen
bool OpenGLHostDisplay::CreateGLContext(bool debug_device)
{
- SDL_SysWMinfo syswm = {};
- SDL_VERSION(&syswm.version);
- if (!SDL_GetWindowWMInfo(m_window, &syswm))
- {
- Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
+ std::optional wi = SDLUtil::GetWindowInfoForSDLWindow(m_window);
+ if (!wi)
return false;
- }
- int window_width, window_height;
- SDL_GetWindowSize(m_window, &window_width, &window_height);
-
- WindowInfo wi;
- wi.surface_width = static_cast(window_width);
- wi.surface_height = static_cast(window_height);
- wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
-
- switch (syswm.subsystem)
- {
-#ifdef SDL_VIDEO_DRIVER_WINDOWS
- case SDL_SYSWM_WINDOWS:
- wi.type = WindowInfo::Type::Win32;
- wi.window_handle = syswm.info.win.window;
- break;
-#endif
-
-#ifdef SDL_VIDEO_DRIVER_COCOA
- case SDL_SYSWM_COCOA:
- wi.type = WindowInfo::Type::MacOS;
- wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
- break;
-#endif
-
-#ifdef SDL_VIDEO_DRIVER_X11
- case SDL_SYSWM_X11:
- wi.type = WindowInfo::Type::X11;
- wi.window_handle = reinterpret_cast(static_cast(syswm.info.x11.window));
- wi.display_connection = syswm.info.x11.display;
- break;
-#endif
-
- default:
- Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem));
- return false;
- }
-
- m_gl_context = GL::Context::Create(wi);
+ m_gl_context = GL::Context::Create(wi.value());
if (!m_gl_context)
{
Log_ErrorPrintf("Failed to create a GL context of any kind.");
@@ -298,6 +247,7 @@ bool OpenGLHostDisplay::CreateImGuiContext()
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(m_window);
+ ImGui::NewFrame();
return true;
}
diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp
index 912247aba..49260089e 100644
--- a/src/duckstation-sdl/sdl_host_interface.cpp
+++ b/src/duckstation-sdl/sdl_host_interface.cpp
@@ -17,6 +17,7 @@
#include "opengl_host_display.h"
#include "scmversion/scmversion.h"
#include "sdl_key_names.h"
+#include "sdl_vulkan_host_display.h"
#include
#include
#include
@@ -129,13 +130,29 @@ void SDLHostInterface::DestroySDLWindow()
bool SDLHostInterface::CreateDisplay()
{
const bool debug_device = m_settings.gpu_use_debug_device;
+ const std::string shader_cache_directory(GetShaderCacheDirectory());
std::unique_ptr display;
-#ifdef WIN32
- display = UseOpenGLRenderer() ? OpenGLHostDisplay::Create(m_window, debug_device) :
- D3D11HostDisplay::Create(m_window, debug_device);
-#else
- display = OpenGLHostDisplay::Create(m_window, debug_device);
+
+ switch (m_settings.gpu_renderer)
+ {
+ case GPURenderer::HardwareVulkan:
+ display = SDLVulkanHostDisplay::Create(m_window, shader_cache_directory, debug_device);
+ break;
+
+ case GPURenderer::HardwareOpenGL:
+#ifndef WIN32
+ default:
#endif
+ display = OpenGLHostDisplay::Create(m_window, debug_device);
+ break;
+
+#ifdef WIN32
+ case GPURenderer::HardwareD3D11:
+ default:
+ display = D3D11HostDisplay::Create(m_window, debug_device);
+ break;
+#endif
+ }
if (!display)
return false;
@@ -181,13 +198,32 @@ void SDLHostInterface::UpdateFramebufferScale()
bool SDLHostInterface::AcquireHostDisplay()
{
- // Handle renderer switch if required on Windows.
-#ifdef WIN32
+ // Handle renderer switch if required.
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
- const bool render_api_is_gl =
- render_api == HostDisplay::RenderAPI::OpenGL || render_api == HostDisplay::RenderAPI::OpenGLES;
- const bool render_api_wants_gl = UseOpenGLRenderer();
- if (render_api_is_gl != render_api_wants_gl)
+ bool needs_switch = false;
+ switch (m_settings.gpu_renderer)
+ {
+#ifdef WIN32
+ case GPURenderer::HardwareD3D11:
+ needs_switch = (render_api != HostDisplay::RenderAPI::D3D11);
+ break;
+#endif
+
+ case GPURenderer::HardwareVulkan:
+ needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan);
+ break;
+
+ case GPURenderer::HardwareOpenGL:
+ needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES);
+ break;
+
+ case GPURenderer::Software:
+ default:
+ needs_switch = false;
+ break;
+ }
+
+ if (needs_switch)
{
ImGui::EndFrame();
DestroyDisplay();
@@ -198,10 +234,7 @@ bool SDLHostInterface::AcquireHostDisplay()
if (!CreateDisplay())
Panic("Failed to recreate display on GPU renderer switch");
-
- ImGui::NewFrame();
}
-#endif
return true;
}
@@ -358,8 +391,6 @@ bool SDLHostInterface::Initialize()
RegisterHotkeys();
- ImGui::NewFrame();
-
// process events to pick up controllers before updating input map
ProcessEvents();
UpdateInputMap();
diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h
index 3625b4de0..ad019fbcd 100644
--- a/src/duckstation-sdl/sdl_host_interface.h
+++ b/src/duckstation-sdl/sdl_host_interface.h
@@ -58,12 +58,6 @@ protected:
private:
bool HasSystem() const { return static_cast(m_system); }
-#ifdef WIN32
- bool UseOpenGLRenderer() const { return m_settings.gpu_renderer == GPURenderer::HardwareOpenGL; }
-#else
- bool UseOpenGLRenderer() const { return true; }
-#endif
-
static float GetDPIScaleFactor(SDL_Window* window);
bool CreateSDLWindow();
diff --git a/src/duckstation-sdl/sdl_util.cpp b/src/duckstation-sdl/sdl_util.cpp
new file mode 100644
index 000000000..82a1b33cf
--- /dev/null
+++ b/src/duckstation-sdl/sdl_util.cpp
@@ -0,0 +1,68 @@
+#include "sdl_util.h"
+#include "common/log.h"
+#include
+Log_SetChannel(SDLUtil);
+
+#ifdef __APPLE__
+#include
+struct NSView;
+
+static NSView* GetContentViewFromWindow(NSWindow* window)
+{
+ // window.contentView
+ return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView"));
+}
+#endif
+
+namespace SDLUtil {
+
+std::optional GetWindowInfoForSDLWindow(SDL_Window* window)
+{
+ SDL_SysWMinfo syswm = {};
+ SDL_VERSION(&syswm.version);
+ if (!SDL_GetWindowWMInfo(window, &syswm))
+ {
+ Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
+ return std::nullopt;
+ }
+
+ int window_width, window_height;
+ SDL_GetWindowSize(window, &window_width, &window_height);
+
+ WindowInfo wi;
+ wi.surface_width = static_cast(window_width);
+ wi.surface_height = static_cast(window_height);
+ wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
+
+ switch (syswm.subsystem)
+ {
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+ case SDL_SYSWM_WINDOWS:
+ wi.type = WindowInfo::Type::Win32;
+ wi.window_handle = syswm.info.win.window;
+ break;
+#endif
+
+#ifdef SDL_VIDEO_DRIVER_COCOA
+ case SDL_SYSWM_COCOA:
+ wi.type = WindowInfo::Type::MacOS;
+ wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
+ break;
+#endif
+
+#ifdef SDL_VIDEO_DRIVER_X11
+ case SDL_SYSWM_X11:
+ wi.type = WindowInfo::Type::X11;
+ wi.window_handle = reinterpret_cast(static_cast(syswm.info.x11.window));
+ wi.display_connection = syswm.info.x11.display;
+ break;
+#endif
+
+ default:
+ Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem));
+ return std::nullopt;
+ }
+
+ return wi;
+}
+} // namespace SDLUtil
\ No newline at end of file
diff --git a/src/duckstation-sdl/sdl_util.h b/src/duckstation-sdl/sdl_util.h
new file mode 100644
index 000000000..ce00e7818
--- /dev/null
+++ b/src/duckstation-sdl/sdl_util.h
@@ -0,0 +1,10 @@
+#pragma once
+#include "common/types.h"
+#include "common/window_info.h"
+#include
+
+struct SDL_Window;
+
+namespace SDLUtil {
+std::optional GetWindowInfoForSDLWindow(SDL_Window* window);
+}
\ No newline at end of file
diff --git a/src/duckstation-sdl/sdl_vulkan_host_display.cpp b/src/duckstation-sdl/sdl_vulkan_host_display.cpp
new file mode 100644
index 000000000..9262b211e
--- /dev/null
+++ b/src/duckstation-sdl/sdl_vulkan_host_display.cpp
@@ -0,0 +1,134 @@
+#include "sdl_vulkan_host_display.h"
+#include "common/assert.h"
+#include "common/log.h"
+#include "imgui.h"
+#include "imgui_impl_sdl.h"
+#include "imgui_impl_vulkan.h"
+#include "sdl_util.h"
+#include
+#include
+Log_SetChannel(VulkanHostDisplay);
+
+SDLVulkanHostDisplay::SDLVulkanHostDisplay(SDL_Window* window) : m_window(window)
+{
+ SDL_GetWindowSize(window, &m_window_width, &m_window_height);
+}
+
+SDLVulkanHostDisplay::~SDLVulkanHostDisplay()
+{
+ ImGui_ImplSDL2_Shutdown();
+ m_display.DestroyImGuiContext();
+ m_display.DestroyResources();
+ m_display.DestroyShaderCache();
+ m_display.DestroySwapChain();
+ m_display.DestroyContext();
+
+ if (m_window)
+ SDL_DestroyWindow(m_window);
+}
+
+std::unique_ptr SDLVulkanHostDisplay::Create(SDL_Window* window, std::string_view shader_cache_directory,
+ bool debug_device)
+{
+ std::unique_ptr display = std::make_unique(window);
+ if (!display->Initialize(shader_cache_directory, debug_device))
+ return nullptr;
+
+ return display;
+}
+
+HostDisplay::RenderAPI SDLVulkanHostDisplay::GetRenderAPI() const
+{
+ return m_display.GetRenderAPI();
+}
+
+void* SDLVulkanHostDisplay::GetRenderDevice() const
+{
+ return m_display.GetRenderDevice();
+}
+
+void* SDLVulkanHostDisplay::GetRenderContext() const
+{
+ return m_display.GetRenderContext();
+}
+
+void SDLVulkanHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height)
+{
+ m_display.ResizeSwapChain(static_cast(new_window_width), static_cast(new_window_height));
+ m_window_width = static_cast(m_display.GetSwapChainWidth());
+ m_window_height = static_cast(m_display.GetSwapChainHeight());
+}
+
+std::unique_ptr SDLVulkanHostDisplay::CreateTexture(u32 width, u32 height, const void* data,
+ u32 data_stride, bool dynamic /*= false*/)
+{
+ return m_display.CreateTexture(width, height, data, data_stride, dynamic);
+}
+
+void SDLVulkanHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height,
+ const void* data, u32 data_stride)
+{
+ m_display.UpdateTexture(texture, x, y, width, height, data, data_stride);
+}
+
+bool SDLVulkanHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height,
+ void* out_data, u32 out_data_stride)
+{
+ return m_display.DownloadTexture(texture_handle, x, y, width, height, out_data, out_data_stride);
+}
+
+void SDLVulkanHostDisplay::SetVSync(bool enabled)
+{
+ m_display.SetVSync(enabled);
+}
+
+bool SDLVulkanHostDisplay::Initialize(std::string_view shader_cache_directory, bool debug_device)
+{
+ std::optional wi = SDLUtil::GetWindowInfoForSDLWindow(m_window);
+ if (!wi.has_value())
+ {
+ Log_ErrorPrintf("Failed to get window info for SDL window");
+ return false;
+ }
+
+ if (!m_display.CreateContextAndSwapChain(wi.value(), debug_device))
+ return false;
+
+ m_display.CreateShaderCache(shader_cache_directory, debug_device);
+
+ if (!m_display.CreateResources())
+ return false;
+
+ if (!m_display.CreateImGuiContext() || !ImGui_ImplSDL2_InitForVulkan(m_window))
+ return false;
+
+ ImGui_ImplSDL2_NewFrame(m_window);
+ ImGui::NewFrame();
+ return true;
+}
+
+void SDLVulkanHostDisplay::Render()
+{
+ if (!m_display.BeginRender())
+ return;
+
+ if (HasDisplayTexture())
+ {
+ const auto [left, top, width, height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
+ m_display.RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width,
+ m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y,
+ m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering);
+ }
+
+ m_display.RenderImGui();
+
+ if (HasSoftwareCursor())
+ {
+ const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect();
+ m_display.RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get());
+ }
+
+ m_display.EndRenderAndPresent();
+
+ ImGui_ImplSDL2_NewFrame(m_window);
+}
diff --git a/src/duckstation-sdl/sdl_vulkan_host_display.h b/src/duckstation-sdl/sdl_vulkan_host_display.h
new file mode 100644
index 000000000..76032e945
--- /dev/null
+++ b/src/duckstation-sdl/sdl_vulkan_host_display.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "core/host_display.h"
+#include "frontend-common/vulkan_host_display.h"
+#include
+#include
+
+class SDLVulkanHostDisplay final : public HostDisplay
+{
+public:
+ SDLVulkanHostDisplay(SDL_Window* window);
+ ~SDLVulkanHostDisplay();
+
+ static std::unique_ptr Create(SDL_Window* window, std::string_view shader_cache_directory,
+ bool debug_device);
+
+ RenderAPI GetRenderAPI() const override;
+ void* GetRenderDevice() const override;
+ void* GetRenderContext() const override;
+
+ void WindowResized(s32 new_window_width, s32 new_window_height) override;
+
+ std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride,
+ bool dynamic = false) override;
+
+ void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
+ u32 data_stride) override;
+
+ bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data,
+ u32 out_data_stride) override;
+
+ void SetVSync(bool enabled) override;
+
+ void Render() override;
+
+private:
+ bool Initialize(std::string_view shader_cache_directory, bool debug_device);
+
+ SDL_Window* m_window = nullptr;
+ FrontendCommon::VulkanHostDisplay m_display;
+};