diff options
| author | Léo Lam <leo@leolam.fr> | 2021-01-28 01:33:46 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-28 01:33:46 +0100 |
| commit | 9ca24ae8e0da53d09e402d18055f102440af7098 (patch) | |
| tree | c1afad8f3611bf1cd49383100f21a635462269c1 /Source/Core/VideoBackends/Vulkan/VKMain.cpp | |
| parent | 4740c2a882fc13fe93adae74139caadc0917c100 (diff) | |
| parent | 500a694ca870f9f5a8ea196f4099a0e7de09fe50 (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.cpp | 361 |
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 |
