summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/VKMain.cpp
diff options
context:
space:
mode:
authorLéo Lam <leo@leolam.fr>2021-01-28 01:33:46 +0100
committerGitHub <noreply@github.com>2021-01-28 01:33:46 +0100
commit9ca24ae8e0da53d09e402d18055f102440af7098 (patch)
treec1afad8f3611bf1cd49383100f21a635462269c1 /Source/Core/VideoBackends/Vulkan/VKMain.cpp
parent4740c2a882fc13fe93adae74139caadc0917c100 (diff)
parent500a694ca870f9f5a8ea196f4099a0e7de09fe50 (diff)
Merge pull request #9092 from shuffle2/vshack
msbuild: Merge "Core" libs into single DolphinLib
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/VKMain.cpp')
-rw-r--r--Source/Core/VideoBackends/Vulkan/VKMain.cpp361
1 files changed, 361 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/VKMain.cpp b/Source/Core/VideoBackends/Vulkan/VKMain.cpp
new file mode 100644
index 0000000000..37cc754449
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/VKMain.cpp
@@ -0,0 +1,361 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "Common/Logging/LogManager.h"
+#include "Common/MsgHandler.h"
+
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/Constants.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/StateTracker.h"
+#include "VideoBackends/Vulkan/VKPerfQuery.h"
+#include "VideoBackends/Vulkan/VKRenderer.h"
+#include "VideoBackends/Vulkan/VKSwapChain.h"
+#include "VideoBackends/Vulkan/VKVertexManager.h"
+#include "VideoBackends/Vulkan/VideoBackend.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#include "VideoCommon/FramebufferManager.h"
+#include "VideoCommon/TextureCacheBase.h"
+#include "VideoCommon/VideoBackendBase.h"
+#include "VideoCommon/VideoConfig.h"
+
+#if defined(VK_USE_PLATFORM_METAL_EXT)
+#include <objc/message.h>
+#endif
+
+namespace Vulkan
+{
+void VideoBackend::InitBackendInfo()
+{
+ VulkanContext::PopulateBackendInfo(&g_Config);
+
+ if (LoadVulkanLibrary())
+ {
+ VkInstance temp_instance =
+ VulkanContext::CreateVulkanInstance(WindowSystemType::Headless, false, false);
+ if (temp_instance)
+ {
+ if (LoadVulkanInstanceFunctions(temp_instance))
+ {
+ VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(temp_instance);
+ VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list);
+
+ if (!gpu_list.empty())
+ {
+ // Use the selected adapter, or the first to fill features.
+ size_t device_index = static_cast<size_t>(g_Config.iAdapter);
+ if (device_index >= gpu_list.size())
+ device_index = 0;
+
+ VkPhysicalDevice gpu = gpu_list[device_index];
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(gpu, &properties);
+ VkPhysicalDeviceFeatures features;
+ vkGetPhysicalDeviceFeatures(gpu, &features);
+ VulkanContext::PopulateBackendInfoFeatures(&g_Config, gpu, properties, features);
+ VulkanContext::PopulateBackendInfoMultisampleModes(&g_Config, gpu, properties);
+ }
+ }
+
+ vkDestroyInstance(temp_instance, nullptr);
+ }
+ else
+ {
+ PanicAlertFmt("Failed to create Vulkan instance.");
+ }
+
+ UnloadVulkanLibrary();
+ }
+ else
+ {
+ PanicAlertFmt("Failed to load Vulkan library.");
+ }
+}
+
+// Helper method to check whether the Host GPU logging category is enabled.
+static bool IsHostGPULoggingEnabled()
+{
+ return Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::HOST_GPU,
+ Common::Log::LERROR);
+}
+
+// Helper method to determine whether to enable the debug report extension.
+static bool ShouldEnableDebugReports(bool enable_validation_layers)
+{
+ // Enable debug reports if the Host GPU log option is checked, or validation layers are enabled.
+ // The only issue here is that if Host GPU is not checked when the instance is created, the debug
+ // report extension will not be enabled, requiring the game to be restarted before any reports
+ // will be logged. Otherwise, we'd have to enable debug reports on every instance, when most
+ // users will never check the Host GPU logging category.
+ return enable_validation_layers || IsHostGPULoggingEnabled();
+}
+
+bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
+{
+ if (!LoadVulkanLibrary())
+ {
+ PanicAlertFmt("Failed to load Vulkan library.");
+ return false;
+ }
+
+ // Check for presence of the validation layers before trying to enable it
+ bool enable_validation_layer = g_Config.bEnableValidationLayer;
+ if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility())
+ {
+ WARN_LOG_FMT(VIDEO, "Validation layer requested but not available, disabling.");
+ enable_validation_layer = false;
+ }
+
+ // Create Vulkan instance, needed before we can create a surface, or enumerate devices.
+ // We use this instance to fill in backend info, then re-use it for the actual device.
+ bool enable_surface = wsi.type != WindowSystemType::Headless;
+ bool enable_debug_reports = ShouldEnableDebugReports(enable_validation_layer);
+ VkInstance instance =
+ VulkanContext::CreateVulkanInstance(wsi.type, enable_debug_reports, enable_validation_layer);
+ if (instance == VK_NULL_HANDLE)
+ {
+ PanicAlertFmt("Failed to create Vulkan instance.");
+ UnloadVulkanLibrary();
+ return false;
+ }
+
+ // Load instance function pointers.
+ if (!LoadVulkanInstanceFunctions(instance))
+ {
+ PanicAlertFmt("Failed to load Vulkan instance functions.");
+ vkDestroyInstance(instance, nullptr);
+ UnloadVulkanLibrary();
+ return false;
+ }
+
+ // Obtain a list of physical devices (GPUs) from the instance.
+ // We'll re-use this list later when creating the device.
+ VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(instance);
+ if (gpu_list.empty())
+ {
+ PanicAlertFmt("No Vulkan physical devices available.");
+ vkDestroyInstance(instance, nullptr);
+ UnloadVulkanLibrary();
+ return false;
+ }
+
+ // Populate BackendInfo with as much information as we can at this point.
+ VulkanContext::PopulateBackendInfo(&g_Config);
+ VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list);
+
+ // 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);
+ if (surface == VK_NULL_HANDLE)
+ {
+ PanicAlertFmt("Failed to create Vulkan surface.");
+ vkDestroyInstance(instance, nullptr);
+ UnloadVulkanLibrary();
+ return false;
+ }
+ }
+
+ // 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_reports, enable_validation_layer);
+ if (!g_vulkan_context)
+ {
+ PanicAlertFmt("Failed to create Vulkan device");
+ UnloadVulkanLibrary();
+ return false;
+ }
+
+ // Since VulkanContext maintains a copy of the device features and properties, we can use this
+ // to initialize the backend information, so that we don't need to enumerate everything again.
+ VulkanContext::PopulateBackendInfoFeatures(&g_Config, g_vulkan_context->GetPhysicalDevice(),
+ g_vulkan_context->GetDeviceProperties(),
+ g_vulkan_context->GetDeviceFeatures());
+ VulkanContext::PopulateBackendInfoMultisampleModes(
+ &g_Config, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceProperties());
+ g_Config.backend_info.bSupportsExclusiveFullscreen =
+ enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface);
+
+ // With the backend information populated, we can now initialize videocommon.
+ InitializeShared();
+
+ // Create command buffers. We do this separately because the other classes depend on it.
+ g_command_buffer_mgr = std::make_unique<CommandBufferManager>(g_Config.bBackendMultithreading);
+ if (!g_command_buffer_mgr->Initialize())
+ {
+ PanicAlertFmt("Failed to create Vulkan command buffers");
+ Shutdown();
+ return false;
+ }
+
+ // Remaining classes are also dependent on object cache.
+ g_object_cache = std::make_unique<ObjectCache>();
+ if (!g_object_cache->Initialize())
+ {
+ PanicAlertFmt("Failed to initialize Vulkan object cache.");
+ Shutdown();
+ return false;
+ }
+
+ // Create swap chain. This has to be done early so that the target size is correct for auto-scale.
+ std::unique_ptr<SwapChain> swap_chain;
+ if (surface != VK_NULL_HANDLE)
+ {
+ swap_chain = SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive);
+ if (!swap_chain)
+ {
+ PanicAlertFmt("Failed to create Vulkan swap chain.");
+ Shutdown();
+ return false;
+ }
+ }
+
+ if (!StateTracker::CreateInstance())
+ {
+ PanicAlertFmt("Failed to create state tracker");
+ Shutdown();
+ return false;
+ }
+
+ // Create main wrapper instances.
+ g_renderer = std::make_unique<Renderer>(std::move(swap_chain), wsi.render_surface_scale);
+ g_vertex_manager = std::make_unique<VertexManager>();
+ g_shader_cache = std::make_unique<VideoCommon::ShaderCache>();
+ g_framebuffer_manager = std::make_unique<FramebufferManager>();
+ g_texture_cache = std::make_unique<TextureCacheBase>();
+ g_perf_query = std::make_unique<PerfQuery>();
+
+ if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() ||
+ !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() ||
+ !g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize())
+ {
+ PanicAlertFmt("Failed to initialize renderer classes");
+ Shutdown();
+ return false;
+ }
+
+ g_shader_cache->InitializeShaderCache();
+ return true;
+}
+
+void VideoBackend::Shutdown()
+{
+ if (g_vulkan_context)
+ vkDeviceWaitIdle(g_vulkan_context->GetDevice());
+
+ if (g_shader_cache)
+ g_shader_cache->Shutdown();
+
+ if (g_object_cache)
+ g_object_cache->Shutdown();
+
+ if (g_renderer)
+ g_renderer->Shutdown();
+
+ g_perf_query.reset();
+ g_texture_cache.reset();
+ g_framebuffer_manager.reset();
+ g_shader_cache.reset();
+ g_vertex_manager.reset();
+ g_renderer.reset();
+ g_object_cache.reset();
+ StateTracker::DestroyInstance();
+ g_command_buffer_mgr.reset();
+ g_vulkan_context.reset();
+ ShutdownShared();
+ UnloadVulkanLibrary();
+}
+
+#if defined(VK_USE_PLATFORM_METAL_EXT)
+static bool IsRunningOnMojaveOrHigher()
+{
+ // id processInfo = [NSProcessInfo processInfo]
+ id processInfo = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(
+ objc_getClass("NSProcessInfo"), sel_getUid("processInfo"));
+ if (!processInfo)
+ return false;
+
+ struct OSVersion // NSOperatingSystemVersion
+ {
+ size_t major_version; // NSInteger majorVersion
+ size_t minor_version; // NSInteger minorVersion
+ size_t patch_version; // NSInteger patchVersion
+ };
+
+ // const bool meets_requirement = [processInfo isOperatingSystemAtLeastVersion:required_version];
+ constexpr OSVersion required_version = {10, 14, 0};
+ const bool meets_requirement = reinterpret_cast<bool (*)(id, SEL, OSVersion)>(objc_msgSend)(
+ processInfo, sel_getUid("isOperatingSystemAtLeastVersion:"), required_version);
+ return meets_requirement;
+}
+#endif
+
+void VideoBackend::PrepareWindow(WindowSystemInfo& wsi)
+{
+#if defined(VK_USE_PLATFORM_METAL_EXT)
+ // This is kinda messy, but it avoids having to write Objective C++ just to create a metal layer.
+ id view = reinterpret_cast<id>(wsi.render_surface);
+ Class clsCAMetalLayer = objc_getClass("CAMetalLayer");
+ if (!clsCAMetalLayer)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to get CAMetalLayer class.");
+ return;
+ }
+
+ // [CAMetalLayer layer]
+ id layer = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("CAMetalLayer"),
+ sel_getUid("layer"));
+ if (!layer)
+ {
+ ERROR_LOG_FMT(VIDEO, "Failed to create Metal layer.");
+ return;
+ }
+
+ // [view setWantsLayer:YES]
+ reinterpret_cast<void (*)(id, SEL, BOOL)>(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES);
+
+ // [view setLayer:layer]
+ reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend)(view, sel_getUid("setLayer:"), layer);
+
+ // NSScreen* screen = [NSScreen mainScreen]
+ id screen = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("NSScreen"),
+ sel_getUid("mainScreen"));
+
+ // CGFloat factor = [screen backingScaleFactor]
+ double factor =
+ reinterpret_cast<double (*)(id, SEL)>(objc_msgSend)(screen, sel_getUid("backingScaleFactor"));
+
+ // layer.contentsScale = factor
+ reinterpret_cast<void (*)(id, SEL, double)>(objc_msgSend)(layer, sel_getUid("setContentsScale:"),
+ factor);
+
+ // Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread.
+ wsi.render_surface = layer;
+
+ // The Metal version included with MacOS 10.13 and below does not support several features we
+ // require. Furthermore, the drivers seem to choke on our shaders (mainly Intel). So, we warn
+ // the user that this is an unsupported configuration, but permit them to continue.
+ if (!IsRunningOnMojaveOrHigher())
+ {
+ PanicAlertFmtT(
+ "You are attempting to use the Vulkan (Metal) backend on an unsupported operating system. "
+ "For all functionality to be enabled, you must use macOS 10.14 (Mojave) or newer. Please "
+ "do not report any issues encountered unless they also occur on 10.14+.");
+ }
+#endif
+}
+} // namespace Vulkan