Skip to content

Commit

Permalink
Vulkan DRM/KMS Support
Browse files Browse the repository at this point in the history
  • Loading branch information
camdenorrb committed Dec 30, 2024
1 parent ada646a commit 128e048
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 17 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current gam
option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON)
option(ENABLE_AUTOUPDATE "Enables support for automatic updates" ON)
option(USE_RETRO_ACHIEVEMENTS "Enables integration with retroachievements.org" ON)
option(ENABLE_DRM "Enables DRM support" OFF)

# Maintainers: if you consider blanket disabling this for your users, please
# consider the following points:
Expand Down Expand Up @@ -501,6 +502,10 @@ if (OPENGL_GL)
include_directories(${OPENGL_INCLUDE_DIR})
endif()

if(ENABLE_DRM)
add_definitions(-DHAVE_DRM)
endif()

if(ENABLE_X11)
pkg_check_modules(X11 x11 IMPORTED_TARGET)
if(X11_FOUND)
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Common/WindowSystemInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum class WindowSystemType
Wayland,
FBDev,
Haiku,
DRM,
};

struct WindowSystemInfo
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/DolphinNoGUI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_sources(dolphin-nogui PRIVATE PlatformFBDev.cpp)
endif()

if(ENABLE_DRM)
target_sources(dolphin-nogui PRIVATE PlatformDRM.cpp)
endif()

set_target_properties(dolphin-nogui PROPERTIES OUTPUT_NAME dolphin-emu-nogui)

target_link_libraries(dolphin-nogui
Expand Down
9 changes: 9 additions & 0 deletions Source/Core/DolphinNoGUI/MainNoGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ static std::unique_ptr<Platform> GetPlatform(const optparse::Values& options)
{
std::string platform_name = static_cast<const char*>(options.get("platform"));

#if HAVE_DRM
if (platform_name == "drm" || platform_name.empty())
return Platform::CreateDRMPlatform();
#endif

#if HAVE_X11
if (platform_name == "x11" || platform_name.empty())
return Platform::CreateX11Platform();
Expand Down Expand Up @@ -215,6 +220,10 @@ int main(int argc, char* argv[])
,
"fbdev"
#endif
#if HAVE_DRM
,
"drm"
#endif
#if HAVE_X11
,
"x11"
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/DolphinNoGUI/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Platform
// Request an immediate shutdown.
void Stop();

#if HAVE_DRM
static std::unique_ptr<Platform> CreateDRMPlatform();
#endif

static std::unique_ptr<Platform> CreateHeadlessPlatform();
#ifdef HAVE_X11
static std::unique_ptr<Platform> CreateX11Platform();
Expand Down
63 changes: 63 additions & 0 deletions Source/Core/DolphinNoGUI/PlatformDRM.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <unistd.h>

#include "DolphinNoGUI/Platform.h"

#include "Common/MsgHandler.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/State.h"
#include "Core/System.h"

#include <climits>
#include <cstdio>
#include <thread>

#include <fcntl.h>
#include <sys/types.h>

namespace
{
class PlatformDRM : public Platform
{
public:
void SetTitle(const std::string& string) override;
void MainLoop() override;

WindowSystemInfo GetWindowSystemInfo() const override;
};

void PlatformDRM::SetTitle(const std::string& string)
{
std::fprintf(stdout, "%s\n", string.c_str());
}

void PlatformDRM::MainLoop()
{
while (IsRunning())
{
UpdateRunningFlag();
Core::HostDispatchJobs(Core::System::GetInstance());

// TODO: Is this sleep appropriate?
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}

WindowSystemInfo PlatformDRM::GetWindowSystemInfo() const
{
WindowSystemInfo wsi;
wsi.type = WindowSystemType::DRM;
wsi.display_connection = nullptr; // EGL_DEFAULT_DISPLAY
wsi.render_window = nullptr;
wsi.render_surface = nullptr;
return wsi;
}
} // namespace

std::unique_ptr<Platform> Platform::CreateDRMPlatform()
{
return std::make_unique<PlatformDRM>();
}
8 changes: 4 additions & 4 deletions Source/Core/VideoBackends/Vulkan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ endif()

# Only include the Vulkan headers when building the Vulkan backend
target_include_directories(videovulkan
PRIVATE
${CMAKE_SOURCE_DIR}/Externals/Vulkan-Headers/include
${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
${CMAKE_SOURCE_DIR}/Externals/libadrenotools/include
PRIVATE
${CMAKE_SOURCE_DIR}/Externals/Vulkan-Headers/include
${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include
${CMAKE_SOURCE_DIR}/Externals/libadrenotools/include
)

if(MSVC)
Expand Down
20 changes: 10 additions & 10 deletions Source/Core/VideoBackends/Vulkan/VKMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,20 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
VulkanContext::PopulateBackendInfo(&g_Config);
VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list);

// Since we haven't called InitializeShared yet, iAdapter may be out of range,
// so we have to check it ourselves.
size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter);
if (selected_adapter_index >= gpu_list.size())
{
WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter.");
selected_adapter_index = 0;
}

// We need the surface before we can create a device, as some parameters depend on it.
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (enable_surface)
{
surface = SwapChain::CreateVulkanSurface(instance, wsi);
surface = SwapChain::CreateVulkanSurface(instance, gpu_list[selected_adapter_index], wsi);
if (surface == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to create Vulkan surface.");
Expand All @@ -161,15 +170,6 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
}
}

// Since we haven't called InitializeShared yet, iAdapter may be out of range,
// so we have to check it ourselves.
size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter);
if (selected_adapter_index >= gpu_list.size())
{
WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter.");
selected_adapter_index = 0;
}

// Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface.
g_vulkan_context =
VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, enable_debug_utils,
Expand Down
170 changes: 168 additions & 2 deletions Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,173 @@ SwapChain::~SwapChain()
DestroySurface();
}

VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi)
VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device,
const WindowSystemInfo& wsi)
{
#if defined(VK_USE_PLATFORM_DISPLAY_KHR)
if (wsi.type == WindowSystemType::DRM)
{
// Get the first display
uint32_t display_count = 1;
VkDisplayPropertiesKHR display_props;
if (VkResult err = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_device, &display_count,
&display_props);
err != VK_SUCCESS && err != VK_INCOMPLETE)
{
LOG_VULKAN_ERROR(err, "vkGetPhysicalDeviceDisplayPropertiesKHR failed: ");
return VK_NULL_HANDLE;
}

// Get the first mode of the display
uint32_t mode_count = 0;
if (VkResult err = vkGetDisplayModePropertiesKHR(physical_device, display_props.display,
&mode_count, nullptr);
err != VK_SUCCESS)
{
LOG_VULKAN_ERROR(err, "vkGetDisplayModePropertiesKHR failed: ");
return VK_NULL_HANDLE;
}

if (mode_count == 0)
{
ERROR_LOG_FMT(VIDEO, "Cannot find any mode for the display!");
return VK_NULL_HANDLE;
}

VkDisplayModePropertiesKHR mode_props;
mode_count = 1;
if (VkResult err = vkGetDisplayModePropertiesKHR(physical_device, display_props.display,
&mode_count, &mode_props);
err != VK_SUCCESS && err != VK_INCOMPLETE)
{
LOG_VULKAN_ERROR(err, "vkGetDisplayModePropertiesKHR failed: ");
return VK_NULL_HANDLE;
}

// Get the list of planes
uint32_t plane_count = 0;
if (VkResult err =
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physical_device, &plane_count, nullptr);
err != VK_SUCCESS)
{
LOG_VULKAN_ERROR(err, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR failed: ");
return VK_NULL_HANDLE;
}

if (plane_count == 0)
{
ERROR_LOG_FMT(VIDEO, "No display planes found!");
return VK_NULL_HANDLE;
}

// Find a plane compatible with the display
uint32_t compatible_plane_index = UINT32_MAX;

for (uint32_t plane_index = 0; plane_index < plane_count; plane_index++)
{
// Query the number of displays supported by the plane
display_count = 0;
VkResult err = vkGetDisplayPlaneSupportedDisplaysKHR(physical_device, plane_index,
&display_count, nullptr);
if (err != VK_SUCCESS)
{
LOG_VULKAN_ERROR(err, "vkGetDisplayPlaneSupportedDisplaysKHR (count query) failed: ");
return VK_NULL_HANDLE;
}

if (display_count == 0)
continue; // Skip planes that support no displays

// Allocate memory to hold the supported displays
std::vector<VkDisplayKHR> displays(display_count);
err = vkGetDisplayPlaneSupportedDisplaysKHR(physical_device, plane_index, &display_count,
displays.data());
if (err != VK_SUCCESS)
{
LOG_VULKAN_ERROR(err, "vkGetDisplayPlaneSupportedDisplaysKHR (fetch displays) failed: ");
return VK_NULL_HANDLE;
}

// Check if the target display is among the supported displays
for (const auto& display : displays)
{
if (display == display_props.display)
{
compatible_plane_index = plane_index;
break;
}
}

if (compatible_plane_index != UINT32_MAX)
break; // Exit early if a compatible plane is found
}

if (compatible_plane_index == UINT32_MAX)
{
ERROR_LOG_FMT(VIDEO, "No compatible plane found for the display!");
return VK_NULL_HANDLE;
}

if (compatible_plane_index == UINT32_MAX)
{
ERROR_LOG_FMT(VIDEO, "No compatible plane found for the display!");
return VK_NULL_HANDLE;
}

// Get capabilities of the compatible plane
VkDisplayPlaneCapabilitiesKHR plane_capabilities;
if (VkResult err = vkGetDisplayPlaneCapabilitiesKHR(
physical_device, mode_props.displayMode, compatible_plane_index, &plane_capabilities);
err != VK_SUCCESS)
{
LOG_VULKAN_ERROR(err, "vkGetDisplayPlaneCapabilitiesKHR failed: ");
return VK_NULL_HANDLE;
}

// Find a supported alpha mode
VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
VkDisplayPlaneAlphaFlagBitsKHR alpha_modes[] = {
VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR,
VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR,
VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR,
};

for (auto& curr_alpha_mode : alpha_modes)
{
if (plane_capabilities.supportedAlpha & curr_alpha_mode)
{
alpha_mode = curr_alpha_mode;
break;
}
}

// Create the display surface
VkDisplaySurfaceCreateInfoKHR surface_create_info = {};
surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
surface_create_info.displayMode = mode_props.displayMode;
surface_create_info.planeIndex = compatible_plane_index;
surface_create_info.planeStackIndex = 0;
surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
surface_create_info.globalAlpha = 1.0f;
surface_create_info.alphaMode = alpha_mode;
surface_create_info.imageExtent.width = display_props.physicalResolution.width;
surface_create_info.imageExtent.height = display_props.physicalResolution.height;

VkSurfaceKHR surface;
if (VkResult res =
vkCreateDisplayPlaneSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateDisplayPlaneSurfaceKHR failed: ");
return VK_NULL_HANDLE;
}

return surface;
}

#endif

#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (wsi.type == WindowSystemType::Windows)
{
Expand Down Expand Up @@ -583,7 +748,8 @@ bool SwapChain::RecreateSurface(void* native_handle)

// Re-create the surface with the new native handle
m_wsi.render_surface = native_handle;
m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wsi);
m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(),
g_vulkan_context->GetPhysicalDevice(), m_wsi);
if (m_surface == VK_NULL_HANDLE)
return false;

Expand Down
3 changes: 2 additions & 1 deletion Source/Core/VideoBackends/Vulkan/VKSwapChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class SwapChain
~SwapChain();

// Creates a vulkan-renderable surface for the specified window handle.
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi);
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device,
const WindowSystemInfo& wsi);

// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<SwapChain> Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface,
Expand Down
Loading

0 comments on commit 128e048

Please sign in to comment.