libretro: Vulkan renderer support

This commit is contained in:
Connor McLaughlin 2020-07-04 20:06:04 +10:00
parent 218f6721d3
commit b9ffca1ddf
16 changed files with 431 additions and 36 deletions

View file

@ -1,7 +1,7 @@
# DuckStation - PlayStation 1, aka. PSX Emulator
**Discord Server:** https://discord.gg/Buktv3t
**Latest Windows and Linux (AppImage) Builds:** https://github.com/stenzek/duckstation/releases/tag/latest
**Latest Windows, Linux (AppImage), and Libretro Builds:** https://github.com/stenzek/duckstation/releases/tag/latest
**Game Compatibility List:** https://docs.google.com/spreadsheets/d/1H66MxViRjjE5f8hOl5RQmF5woS1murio2dsLn14kEqo/edit?usp=sharing
@ -11,6 +11,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
## Latest News
- 2020/07/04: Vulkan renderer now available in libretro core.
- 2020/07/02: Now available as a libretro core.
- 2020/07/01: Lightgun support with custom crosshairs.
- 2020/06/19: Vulkan hardware renderer added.
@ -197,7 +198,7 @@ Hotkeys:
## Libretro Core
DuckStation is available as a libretro core, which can be loaded into a frontend such as RetroArch. Currently, only the D3D11 and OpenGL renderers are available, Vulkan will be available soon. It supports most features of the full frontend, within the constraints and limitations of being a libretro core.
DuckStation is available as a libretro core, which can be loaded into a frontend such as RetroArch. It supports most features of the full frontend, within the constraints and limitations of being a libretro core.
Prebuilt binaries for Windows and 64-bit Linux can be found on the releases page. Direct links:
- 64-bit Windows: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-libretro-windows-x64-release.7z

View file

@ -97,5 +97,6 @@ bool LoadVulkanLibrary();
bool LoadVulkanInstanceFunctions(VkInstance instance);
bool LoadVulkanDeviceFunctions(VkDevice device);
void UnloadVulkanLibrary();
void ResetVulkanLibraryFunctionPointers();
} // namespace Vulkan

View file

@ -23,7 +23,8 @@
#undef VULKAN_MODULE_ENTRY_POINT
namespace Vulkan {
static void ResetVulkanLibraryFunctionPointers()
void ResetVulkanLibraryFunctionPointers()
{
#define VULKAN_MODULE_ENTRY_POINT(name, required) name = nullptr;
#define VULKAN_INSTANCE_ENTRY_POINT(name, required) name = nullptr;

View file

@ -19,8 +19,8 @@ std::unique_ptr<Vulkan::Context> g_vulkan_context;
namespace Vulkan {
Context::Context(VkInstance instance, VkPhysicalDevice physical_device)
: m_instance(instance), m_physical_device(physical_device)
Context::Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device)
: m_instance(instance), m_physical_device(physical_device), m_owns_device(owns_device)
{
// Read device physical memory properties, we need it for allocating buffers
vkGetPhysicalDeviceProperties(physical_device, &m_device_properties);
@ -44,13 +44,17 @@ Context::~Context()
DestroyGlobalDescriptorPool();
DestroyCommandBuffers();
if (m_device != VK_NULL_HANDLE)
if (m_owns_device && m_device != VK_NULL_HANDLE)
vkDestroyDevice(m_device, nullptr);
if (m_debug_report_callback != VK_NULL_HANDLE)
DisableDebugReports();
vkDestroyInstance(m_instance, nullptr);
if (m_owns_device)
{
vkDestroyInstance(m_instance, nullptr);
Vulkan::UnloadVulkanLibrary();
}
}
bool Context::CheckValidationLayerAvailablility()
@ -344,14 +348,14 @@ bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::uniqu
return false;
}
g_vulkan_context.reset(new Context(instance, gpus[gpu_index]));
g_vulkan_context.reset(new Context(instance, gpus[gpu_index], true));
// Enable debug reports if the "Host GPU" log category is enabled.
if (enable_debug_reports)
g_vulkan_context->EnableDebugReports();
// Attempt to create the device.
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer) ||
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) ||
!g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers() ||
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, true)) == nullptr))
{
@ -359,7 +363,33 @@ bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::uniqu
if (surface != VK_NULL_HANDLE)
vkDestroySurfaceKHR(instance, surface, nullptr);
Vulkan::UnloadVulkanLibrary();
g_vulkan_context.reset();
return false;
}
return true;
}
bool Context::CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
bool take_ownership, bool enable_validation_layer, bool enable_debug_reports,
const char** required_device_extensions /* = nullptr */,
u32 num_required_device_extensions /* = 0 */,
const char** required_device_layers /* = nullptr */,
u32 num_required_device_layers /* = 0 */,
const VkPhysicalDeviceFeatures* required_features /* = nullptr */)
{
g_vulkan_context.reset(new Context(instance, gpu, take_ownership));
// Enable debug reports if the "Host GPU" log category is enabled.
if (enable_debug_reports)
g_vulkan_context->EnableDebugReports();
// Attempt to create the device.
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, required_device_extensions,
num_required_device_extensions, required_device_layers,
num_required_device_layers, required_features) ||
!g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers())
{
g_vulkan_context.reset();
return false;
}
@ -403,8 +433,13 @@ bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_
return !strcmp(name, properties.extensionName);
}) != available_extension_list.end())
{
Log_InfoPrintf("Enabling extension: %s", name);
extension_list->push_back(name);
if (std::none_of(extension_list->begin(), extension_list->end(),
[&](const char* existing_name) { return (std::strcmp(existing_name, name) == 0); }))
{
Log_InfoPrintf("Enabling extension: %s", name);
extension_list->push_back(name);
}
return true;
}
@ -420,7 +455,7 @@ bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_
return true;
}
bool Context::SelectDeviceFeatures()
bool Context::SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features)
{
VkPhysicalDeviceFeatures available_features;
vkGetPhysicalDeviceFeatures(m_physical_device, &available_features);
@ -431,6 +466,9 @@ bool Context::SelectDeviceFeatures()
return false;
}
if (required_features)
std::memcpy(&m_device_features, required_features, sizeof(m_device_features));
// Enable the features we use.
m_device_features.dualSrcBlend = available_features.dualSrcBlend;
m_device_features.geometryShader = available_features.geometryShader;
@ -438,7 +476,9 @@ bool Context::SelectDeviceFeatures()
return true;
}
bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,
u32 num_required_device_extensions, const char** required_device_layers,
u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features)
{
u32 queue_family_count;
vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr);
@ -536,16 +576,18 @@ bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
device_info.pQueueCreateInfos = queue_infos.data();
ExtensionList enabled_extensions;
for (u32 i = 0; i < num_required_device_extensions; i++)
enabled_extensions.emplace_back(required_device_extensions[i]);
if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE))
return false;
device_info.enabledLayerCount = 0;
device_info.ppEnabledLayerNames = nullptr;
device_info.enabledLayerCount = num_required_device_layers;
device_info.ppEnabledLayerNames = required_device_layers;
device_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
device_info.ppEnabledExtensionNames = enabled_extensions.data();
// Check for required features before creating.
if (!SelectDeviceFeatures())
if (!SelectDeviceFeatures(required_features))
return false;
device_info.pEnabledFeatures = &m_device_features;

View file

@ -46,6 +46,15 @@ public:
static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
bool enable_debug_reports, bool enable_validation_layer);
// Creates a new context from a pre-existing instance.
static bool CreateFromExistingInstance(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface,
bool take_ownership, bool enable_validation_layer, bool enable_debug_reports,
const char** required_device_extensions = nullptr,
u32 num_required_device_extensions = 0,
const char** required_device_layers = nullptr,
u32 num_required_device_layers = 0,
const VkPhysicalDeviceFeatures* required_features = nullptr);
// Destroys context.
static void Destroy();
@ -162,13 +171,15 @@ public:
void WaitForGPUIdle();
private:
Context(VkInstance instance, VkPhysicalDevice physical_device);
Context(VkInstance instance, VkPhysicalDevice physical_device, bool owns_device);
using ExtensionList = std::vector<const char*>;
static bool SelectInstanceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_debug_report);
bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface);
bool SelectDeviceFeatures();
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer);
bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features);
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,
u32 num_required_device_extensions, const char** required_device_layers,
u32 num_required_device_layers, const VkPhysicalDeviceFeatures* required_features);
bool CreateCommandBuffers();
void DestroyCommandBuffers();
@ -210,6 +221,7 @@ private:
u64 m_completed_fence_counter = 0;
u32 m_current_frame;
bool m_owns_device = false;
bool m_last_present_failed = false;
// Render pass cache

View file

@ -232,6 +232,15 @@ void SafeDestroySampler(VkSampler& samp)
}
}
void SafeDestroySemaphore(VkSemaphore& sem)
{
if (sem != VK_NULL_HANDLE)
{
vkDestroySemaphore(g_vulkan_context->GetDevice(), sem, nullptr);
sem = VK_NULL_HANDLE;
}
}
void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds)
{
if (ds != VK_NULL_HANDLE)

View file

@ -41,6 +41,7 @@ void SafeDestroyPipelineLayout(VkPipelineLayout& pl);
void SafeDestroyDescriptorSetLayout(VkDescriptorSetLayout& dsl);
void SafeDestroyBufferView(VkBufferView& bv);
void SafeDestroySampler(VkSampler& samp);
void SafeDestroySemaphore(VkSemaphore& sem);
void SafeFreeGlobalDescriptorSet(VkDescriptorSet& ds);
void SetViewport(VkCommandBuffer command_buffer, int x, int y, int width, int height, float min_depth = 0.0f,

View file

@ -9,6 +9,8 @@ add_library(duckstation-libretro SHARED
libretro_opengl_host_display.h
libretro_settings_interface.cpp
libretro_settings_interface.h
libretro_vulkan_host_display.cpp
libretro_vulkan_host_display.h
main.cpp
)
@ -19,5 +21,5 @@ if(WIN32)
)
endif()
target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion frontend-common libretro-common)
target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion frontend-common vulkan-loader libretro-common)

View file

@ -35,6 +35,9 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\dep\vulkan-loader\vulkan-loader.vcxproj">
<Project>{9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035}</Project>
</ProjectReference>
<ProjectReference Include="..\common\common.vcxproj">
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
</ProjectReference>
@ -54,6 +57,7 @@
<ClCompile Include="libretro_host_display.cpp" />
<ClCompile Include="libretro_host_interface.cpp" />
<ClCompile Include="libretro_settings_interface.cpp" />
<ClCompile Include="libretro_vulkan_host_display.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="libretro_opengl_host_display.cpp" />
</ItemGroup>
@ -64,6 +68,7 @@
<ClInclude Include="libretro_host_interface.h" />
<ClInclude Include="libretro_settings_interface.h" />
<ClInclude Include="libretro_opengl_host_display.h" />
<ClInclude Include="libretro_vulkan_host_display.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}</ProjectGuid>
@ -211,7 +216,7 @@
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -232,7 +237,7 @@
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -253,7 +258,7 @@
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -277,7 +282,7 @@
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -300,7 +305,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -323,7 +328,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -347,7 +352,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -370,7 +375,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\libretro-common\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>

View file

@ -8,6 +8,7 @@
<ClCompile Include="libretro_settings_interface.cpp" />
<ClCompile Include="libretro_opengl_host_display.cpp" />
<ClCompile Include="libretro_d3d11_host_display.cpp" />
<ClCompile Include="libretro_vulkan_host_display.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="libretro_host_interface.h" />
@ -16,5 +17,6 @@
<ClInclude Include="libretro_settings_interface.h" />
<ClInclude Include="libretro_opengl_host_display.h" />
<ClInclude Include="libretro_d3d11_host_display.h" />
<ClInclude Include="libretro_vulkan_host_display.h" />
</ItemGroup>
</Project>

View file

@ -4,7 +4,7 @@
#include "common/d3d11/shader_compiler.h"
#include "common/log.h"
#include "libretro_host_interface.h"
Log_SetChannel(D3D11HostDisplay);
Log_SetChannel(LibretroD3D11HostDisplay);
#define HAVE_D3D11
#include "libretro_d3d.h"

View file

@ -13,6 +13,7 @@
#include "libretro_host_display.h"
#include "libretro_opengl_host_display.h"
#include "libretro_settings_interface.h"
#include "libretro_vulkan_host_display.h"
#include <array>
#include <cstring>
#include <tuple>
@ -344,6 +345,7 @@ static std::array<retro_core_option_definition, 22> s_option_definitions = {{
{"D3D11", "Hardware (D3D11)"},
#endif
{"OpenGL", "Hardware (OpenGL)"},
{"Vulkan", "Hardware (Vulkan)"},
{"Software", "Software"}},
#ifdef WIN32
"D3D11"
@ -658,16 +660,14 @@ static std::optional<GPURenderer> RetroHwContextToRenderer(retro_hw_context_type
case RETRO_HW_CONTEXT_OPENGLES_VERSION:
return GPURenderer::HardwareOpenGL;
case RETRO_HW_CONTEXT_VULKAN:
return GPURenderer::HardwareVulkan;
#ifdef WIN32
case RETRO_HW_CONTEXT_DIRECT3D:
return GPURenderer::HardwareD3D11;
#endif
#if 0
case RETRO_HW_CONTEXT_VULKAN:
return GPURenderer::HardwareVulkan;
#endif
default:
return std::nullopt;
}
@ -720,6 +720,10 @@ bool LibretroHostInterface::RequestHardwareRendererContext()
break;
#endif
case GPURenderer::HardwareVulkan:
m_hw_render_callback_valid = LibretroVulkanHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback);
break;
case GPURenderer::HardwareOpenGL:
m_hw_render_callback_valid = LibretroOpenGLHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback);
break;
@ -758,6 +762,10 @@ void LibretroHostInterface::SwitchToHardwareRenderer()
display = std::make_unique<LibretroOpenGLHostDisplay>();
break;
case GPURenderer::HardwareVulkan:
display = std::make_unique<LibretroVulkanHostDisplay>();
break;
#ifdef WIN32
case GPURenderer::HardwareD3D11:
display = std::make_unique<LibretroD3D11HostDisplay>();

View file

@ -0,0 +1,261 @@
#include "libretro_vulkan_host_display.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/vulkan/builders.h"
#include "common/vulkan/context.h"
#include "common/vulkan/shader_cache.h"
#include "common/vulkan/util.h"
#include "libretro_host_interface.h"
#include "vulkan_loader.h"
Log_SetChannel(LibretroVulkanHostDisplay);
LibretroVulkanHostDisplay::LibretroVulkanHostDisplay() = default;
LibretroVulkanHostDisplay::~LibretroVulkanHostDisplay() = default;
void LibretroVulkanHostDisplay::SetVSync(bool enabled)
{
// The libretro frontend controls this.
Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled));
}
static bool LoadModuleFunctions(VkInstance instance, PFN_vkGetInstanceProcAddr get_instance_proc_addr)
{
#define VULKAN_MODULE_ENTRY_POINT(name, required) \
if (!name && (name = reinterpret_cast<decltype(name)>(get_instance_proc_addr(instance, #name))) == nullptr) \
{ \
Log_ErrorPrintf("Could not get function pointer for '%s'", #name); \
return false; \
}
#include "vulkan_entry_points.inl"
#undef VULKAN_MODULE_ENTRY_POINT
return true;
}
static bool RetroCreateVulkanDevice(struct retro_vulkan_context* context, VkInstance instance, VkPhysicalDevice gpu,
VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr,
const char** required_device_extensions, unsigned num_required_device_extensions,
const char** required_device_layers, unsigned num_required_device_layers,
const VkPhysicalDeviceFeatures* required_features)
{
// We need some module functions.
vkGetInstanceProcAddr = get_instance_proc_addr;
if (!LoadModuleFunctions(instance, get_instance_proc_addr))
{
Log_ErrorPrintf("Failed to load Vulkan module functions");
Vulkan::ResetVulkanLibraryFunctionPointers();
return false;
}
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
{
Log_ErrorPrintf("Failed to load Vulkan instance functions");
Vulkan::ResetVulkanLibraryFunctionPointers();
return false;
}
if (gpu == VK_NULL_HANDLE)
{
Vulkan::Context::GPUList gpus = Vulkan::Context::EnumerateGPUs(instance);
if (gpus.empty())
{
g_libretro_host_interface.ReportError("No GPU provided and none available, cannot create device");
Vulkan::ResetVulkanLibraryFunctionPointers();
return false;
}
Log_InfoPrintf("No GPU provided, using first/default");
gpu = gpus[0];
}
if (!Vulkan::Context::CreateFromExistingInstance(
instance, gpu, surface, false, false, false, required_device_extensions, num_required_device_extensions,
required_device_layers, num_required_device_layers, required_features))
{
Vulkan::ResetVulkanLibraryFunctionPointers();
return false;
}
context->gpu = g_vulkan_context->GetPhysicalDevice();
context->device = g_vulkan_context->GetDevice();
context->queue = g_vulkan_context->GetGraphicsQueue();
context->queue_family_index = g_vulkan_context->GetGraphicsQueueFamilyIndex();
context->presentation_queue = g_vulkan_context->GetPresentQueue();
context->presentation_queue_family_index = g_vulkan_context->GetPresentQueueFamilyIndex();
return true;
}
static retro_hw_render_context_negotiation_interface_vulkan s_vulkan_context_negotiation_interface = {
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, // interface_type
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, // interface_version
nullptr, // get_application_info
RetroCreateVulkanDevice, // create_device
nullptr // destroy_device
};
bool LibretroVulkanHostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb)
{
cb->cache_context = true;
cb->bottom_left_origin = false;
cb->context_type = RETRO_HW_CONTEXT_VULKAN;
return g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb) &&
g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE,
&s_vulkan_context_negotiation_interface);
}
bool LibretroVulkanHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name,
bool debug_device)
{
retro_hw_render_interface* ri = nullptr;
if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri))
{
Log_ErrorPrint("Failed to get HW render interface");
return false;
}
else if (ri->interface_type != RETRO_HW_RENDER_INTERFACE_VULKAN ||
ri->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION)
{
Log_ErrorPrintf("Unexpected HW interface - type %u version %u", static_cast<unsigned>(ri->interface_type),
static_cast<unsigned>(ri->interface_version));
return false;
}
if (!g_vulkan_context)
{
Log_ErrorPrintf("Vulkan context was not negotiated/created");
return false;
}
// TODO: Grab queue? it should be the same
m_ri = reinterpret_cast<const retro_hw_render_interface_vulkan*>(ri);
return true;
}
void LibretroVulkanHostDisplay::DestroyRenderDevice()
{
VulkanHostDisplay::DestroyRenderDevice();
Vulkan::ResetVulkanLibraryFunctionPointers();
}
bool LibretroVulkanHostDisplay::CreateResources()
{
m_frame_render_pass = g_vulkan_context->GetRenderPass(FRAMEBUFFER_FORMAT, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_CLEAR);
if (m_frame_render_pass == VK_NULL_HANDLE)
return false;
return VulkanHostDisplay::CreateResources();
}
void LibretroVulkanHostDisplay::DestroyResources()
{
VulkanHostDisplay::DestroyResources();
Vulkan::Util::SafeDestroyFramebuffer(m_frame_framebuffer);
m_frame_texture.Destroy();
}
VkRenderPass LibretroVulkanHostDisplay::GetRenderPassForDisplay() const
{
return m_frame_render_pass;
}
void LibretroVulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height)
{
m_window_info.surface_width = static_cast<u32>(new_window_width);
m_window_info.surface_height = static_cast<u32>(new_window_height);
}
bool LibretroVulkanHostDisplay::Render()
{
const u32 resolution_scale = g_libretro_host_interface.GetResolutionScale();
const u32 display_width = static_cast<u32>(m_display_width) * resolution_scale;
const u32 display_height = static_cast<u32>(m_display_height) * resolution_scale;
if (!CheckFramebufferSize(display_width, display_height))
return false;
VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer();
m_frame_texture.OverrideImageLayout(m_frame_view.image_layout);
m_frame_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
const VkClearValue clear_value = {};
const VkRenderPassBeginInfo rp = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_frame_render_pass, m_frame_framebuffer,
{{0, 0}, {display_width, display_height}}, 1u, &clear_value};
vkCmdBeginRenderPass(cmdbuffer, &rp, VK_SUBPASS_CONTENTS_INLINE);
if (HasDisplayTexture())
{
const auto [left, top, width, height] = CalculateDrawRect(display_width, display_height, 0);
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);
}
if (HasSoftwareCursor())
{
// TODO: Scale mouse x/y
const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y);
RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get());
}
vkCmdEndRenderPass(cmdbuffer);
m_frame_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
m_frame_view.image_layout = m_frame_texture.GetLayout();
m_ri->set_image(m_ri->handle, &m_frame_view, 0, nullptr, VK_QUEUE_FAMILY_IGNORED);
// TODO: We can't use this because it doesn't support passing fences...
// m_ri->set_command_buffers(m_ri->handle, 1, &cmdbuffer);
m_ri->lock_queue(m_ri->handle);
g_vulkan_context->SubmitCommandBuffer();
m_ri->unlock_queue(m_ri->handle);
g_vulkan_context->MoveToNextCommandBuffer();
g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, display_width, display_height, 0);
return true;
}
bool LibretroVulkanHostDisplay::CheckFramebufferSize(u32 width, u32 height)
{
static constexpr VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
static constexpr VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D;
static constexpr VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL;
if (m_frame_texture.GetWidth() == width && m_frame_texture.GetHeight() == height)
return true;
g_vulkan_context->DeferFramebufferDestruction(m_frame_framebuffer);
m_frame_texture.Destroy(true);
if (!m_frame_texture.Create(width, height, 1, 1, FRAMEBUFFER_FORMAT, VK_SAMPLE_COUNT_1_BIT, view_type, tiling, usage))
return false;
VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer();
m_frame_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
static constexpr VkClearColorValue cc = {};
static constexpr VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(cmdbuf, m_frame_texture.GetImage(), m_frame_texture.GetLayout(), &cc, 1, &range);
Vulkan::FramebufferBuilder fbb;
fbb.SetRenderPass(m_frame_render_pass);
fbb.AddAttachment(m_frame_texture.GetView());
fbb.SetSize(width, height, 1);
m_frame_framebuffer = fbb.Create(g_vulkan_context->GetDevice(), false);
if (m_frame_framebuffer == VK_NULL_HANDLE)
return false;
m_frame_view = {};
m_frame_view.image_view = m_frame_texture.GetView();
m_frame_view.image_layout = m_frame_texture.GetLayout();
m_frame_view.create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
m_frame_view.create_info.image = m_frame_texture.GetImage();
m_frame_view.create_info.viewType = view_type;
m_frame_view.create_info.format = FRAMEBUFFER_FORMAT;
m_frame_view.create_info.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A};
m_frame_view.create_info.subresourceRange = range;
return true;
}

View file

@ -0,0 +1,42 @@
#pragma once
#include "common/vulkan/texture.h"
#include "frontend-common/vulkan_host_display.h"
#include "libretro.h"
#define HAVE_VULKAN
#include "libretro_vulkan.h"
class LibretroVulkanHostDisplay final : public FrontendCommon::VulkanHostDisplay
{
public:
LibretroVulkanHostDisplay();
~LibretroVulkanHostDisplay();
static bool RequestHardwareRendererContext(retro_hw_render_callback* cb);
bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override;
void DestroyRenderDevice() override;
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
void SetVSync(bool enabled) override;
bool Render() override;
protected:
bool CreateResources() override;
void DestroyResources() override;
VkRenderPass GetRenderPassForDisplay() const override;
private:
static constexpr VkFormat FRAMEBUFFER_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
bool CheckFramebufferSize(u32 width, u32 height);
const retro_hw_render_interface_vulkan* m_ri = nullptr;
Vulkan::Texture m_frame_texture;
retro_vulkan_image m_frame_view = {};
VkFramebuffer m_frame_framebuffer = VK_NULL_HANDLE;
VkRenderPass m_frame_render_pass = VK_NULL_HANDLE;
};

View file

@ -266,6 +266,11 @@ bool VulkanHostDisplay::HasRenderSurface() const
return static_cast<bool>(m_swap_chain);
}
VkRenderPass VulkanHostDisplay::GetRenderPassForDisplay() const
{
return m_swap_chain->GetClearRenderPass();
}
bool VulkanHostDisplay::CreateResources()
{
static constexpr char fullscreen_quad_vertex_shader[] = R"(
@ -348,7 +353,7 @@ void main()
gpbuilder.SetNoBlendingState();
gpbuilder.SetDynamicViewportAndScissorState();
gpbuilder.SetPipelineLayout(m_pipeline_layout);
gpbuilder.SetRenderPass(m_swap_chain->GetClearRenderPass(), 0);
gpbuilder.SetRenderPass(GetRenderPassForDisplay(), 0);
m_display_pipeline = gpbuilder.Create(device, pipeline_cache, false);
if (m_display_pipeline == VK_NULL_HANDLE)

View file

@ -60,6 +60,9 @@ protected:
float src_rect_height;
};
// Can be overridden by frontends.
virtual VkRenderPass GetRenderPassForDisplay() const;
virtual bool CreateResources();
virtual void DestroyResources();